summaryrefslogtreecommitdiffstats
path: root/src/java
diff options
context:
space:
mode:
authorJames Ahlborn <jtahlborn@yahoo.com>2013-03-05 00:16:20 +0000
committerJames Ahlborn <jtahlborn@yahoo.com>2013-03-05 00:16:20 +0000
commit71c3508b8ac2b56698bfe247b95eae5a1f390701 (patch)
treeedb853bb93135673213d42f554f216ed21457a8c /src/java
parent67833826ba430014698a030667561b537b79fe42 (diff)
downloadjackcess-71c3508b8ac2b56698bfe247b95eae5a1f390701.tar.gz
jackcess-71c3508b8ac2b56698bfe247b95eae5a1f390701.zip
move internals of Column into ColumnImpl
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/jackcess-2@671 f203690c-595d-4dc9-a70b-905162fa7fd2
Diffstat (limited to 'src/java')
-rw-r--r--src/java/com/healthmarketscience/jackcess/CaseInsensitiveColumnMatcher.java4
-rw-r--r--src/java/com/healthmarketscience/jackcess/Column.java2349
-rw-r--r--src/java/com/healthmarketscience/jackcess/ColumnBuilder.java6
-rw-r--r--src/java/com/healthmarketscience/jackcess/ColumnImpl.java2393
-rw-r--r--src/java/com/healthmarketscience/jackcess/Cursor.java34
-rw-r--r--src/java/com/healthmarketscience/jackcess/CursorBuilder.java4
-rw-r--r--src/java/com/healthmarketscience/jackcess/DatabaseImpl.java49
-rw-r--r--src/java/com/healthmarketscience/jackcess/ExportFilter.java2
-rw-r--r--src/java/com/healthmarketscience/jackcess/ExportUtil.java8
-rw-r--r--src/java/com/healthmarketscience/jackcess/FKEnforcer.java12
-rw-r--r--src/java/com/healthmarketscience/jackcess/GeneralLegacyIndexCodes.java2
-rw-r--r--src/java/com/healthmarketscience/jackcess/ImportFilter.java2
-rw-r--r--src/java/com/healthmarketscience/jackcess/ImportUtil.java12
-rw-r--r--src/java/com/healthmarketscience/jackcess/Index.java11
-rw-r--r--src/java/com/healthmarketscience/jackcess/IndexCursor.java2
-rw-r--r--src/java/com/healthmarketscience/jackcess/IndexData.java50
-rw-r--r--src/java/com/healthmarketscience/jackcess/IndexImpl.java3
-rw-r--r--src/java/com/healthmarketscience/jackcess/JetFormat.java16
-rw-r--r--src/java/com/healthmarketscience/jackcess/NullMask.java4
-rw-r--r--src/java/com/healthmarketscience/jackcess/PropertyMaps.java4
-rw-r--r--src/java/com/healthmarketscience/jackcess/Relationship.java16
-rw-r--r--src/java/com/healthmarketscience/jackcess/SimpleExportFilter.java2
-rw-r--r--src/java/com/healthmarketscience/jackcess/SimpleImportFilter.java2
-rw-r--r--src/java/com/healthmarketscience/jackcess/Table.java2
-rw-r--r--src/java/com/healthmarketscience/jackcess/TableBuilder.java8
-rw-r--r--src/java/com/healthmarketscience/jackcess/TableCreator.java14
-rw-r--r--src/java/com/healthmarketscience/jackcess/TableImpl.java109
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java46
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java41
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java8
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/MultiValueColumnInfo.java10
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java10
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/VersionHistoryColumnInfo.java24
33 files changed, 2684 insertions, 2575 deletions
diff --git a/src/java/com/healthmarketscience/jackcess/CaseInsensitiveColumnMatcher.java b/src/java/com/healthmarketscience/jackcess/CaseInsensitiveColumnMatcher.java
index a88d0d2..cec63f3 100644
--- a/src/java/com/healthmarketscience/jackcess/CaseInsensitiveColumnMatcher.java
+++ b/src/java/com/healthmarketscience/jackcess/CaseInsensitiveColumnMatcher.java
@@ -49,8 +49,8 @@ public class CaseInsensitiveColumnMatcher implements ColumnMatcher {
// convert both values to Strings and compare case-insensitively
try {
- CharSequence cs1 = Column.toCharSequence(value1);
- CharSequence cs2 = Column.toCharSequence(value2);
+ CharSequence cs1 = ColumnImpl.toCharSequence(value1);
+ CharSequence cs2 = ColumnImpl.toCharSequence(value2);
return((cs1 == cs2) ||
((cs1 != null) && (cs2 != null) &&
diff --git a/src/java/com/healthmarketscience/jackcess/Column.java b/src/java/com/healthmarketscience/jackcess/Column.java
index 4a8ac47..76fcbd9 100644
--- a/src/java/com/healthmarketscience/jackcess/Column.java
+++ b/src/java/com/healthmarketscience/jackcess/Column.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2005 Health Market Science, Inc.
+Copyright (c) 2013 James Ahlborn
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
@@ -15,62 +15,23 @@ You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
-
-You can contact Health Market Science at info@healthmarketscience.com
-or at the following address:
-
-Health Market Science
-2700 Horizon Drive
-Suite 200
-King of Prussia, PA 19406
*/
package com.healthmarketscience.jackcess;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectOutputStream;
-import java.io.ObjectStreamException;
-import java.io.Reader;
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-import java.sql.Blob;
-import java.sql.Clob;
import java.sql.SQLException;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.List;
import java.util.Map;
-import java.util.TimeZone;
-import java.util.UUID;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import com.healthmarketscience.jackcess.complex.ComplexColumnInfo;
import com.healthmarketscience.jackcess.complex.ComplexValue;
-import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
-import com.healthmarketscience.jackcess.scsu.Compress;
-import com.healthmarketscience.jackcess.scsu.EndOfInputException;
-import com.healthmarketscience.jackcess.scsu.Expand;
-import com.healthmarketscience.jackcess.scsu.IllegalInputException;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
/**
- * Access database column definition
- * @author Tim McCune
- * @usage _general_class_
+ *
+ * @author James Ahlborn
*/
-public class Column implements Comparable<Column> {
-
- private static final Log LOG = LogFactory.getLog(Column.class);
-
+public abstract class Column
+{
/**
* Meaningless placeholder object for inserting values in an autonumber
* column. it is not required that this value be used (any passed in value
@@ -87,2359 +48,101 @@ public class Column implements Comparable<Column> {
public static final Object KEEP_VALUE = "<KEEP_VALUE>";
/**
- * Access stores numeric dates in days. Java stores them in milliseconds.
- */
- private static final double MILLISECONDS_PER_DAY =
- (24L * 60L * 60L * 1000L);
-
- /**
- * Access starts counting dates at Jan 1, 1900. Java starts counting
- * at Jan 1, 1970. This is the # of millis between them for conversion.
- */
- private static final long MILLIS_BETWEEN_EPOCH_AND_1900 =
- 25569L * (long)MILLISECONDS_PER_DAY;
-
- /**
- * Long value (LVAL) type that indicates that the value is stored on the
- * same page
- */
- private static final byte LONG_VALUE_TYPE_THIS_PAGE = (byte) 0x80;
- /**
- * Long value (LVAL) type that indicates that the value is stored on another
- * page
- */
- private static final byte LONG_VALUE_TYPE_OTHER_PAGE = (byte) 0x40;
- /**
- * Long value (LVAL) type that indicates that the value is stored on
- * multiple other pages
- */
- private static final byte LONG_VALUE_TYPE_OTHER_PAGES = (byte) 0x00;
- /**
- * Mask to apply the long length in order to get the flag bits (only the
- * first 2 bits are type flags).
- */
- private static final int LONG_VALUE_TYPE_MASK = 0xC0000000;
-
- /**
- * mask for the fixed len bit
- * @usage _advanced_field_
- */
- public static final byte FIXED_LEN_FLAG_MASK = (byte)0x01;
-
- /**
- * mask for the auto number bit
- * @usage _advanced_field_
- */
- public static final byte AUTO_NUMBER_FLAG_MASK = (byte)0x04;
-
- /**
- * mask for the auto number guid bit
- * @usage _advanced_field_
- */
- public static final byte AUTO_NUMBER_GUID_FLAG_MASK = (byte)0x40;
-
- /**
- * mask for the hyperlink bit (on memo types)
- * @usage _advanced_field_
- */
- public static final byte HYPERLINK_FLAG_MASK = (byte)0x80;
-
- /**
- * mask for the unknown bit (possible "can be null"?)
- * @usage _advanced_field_
- */
- public static final byte UNKNOWN_FLAG_MASK = (byte)0x02;
-
- // some other flags?
- // 0x10: replication related field (or hidden?)
- // 0x80: hyperlink (some memo based thing)
-
- /** the value for the "general" sort order */
- private static final short GENERAL_SORT_ORDER_VALUE = 1033;
-
- /**
- * the "general" text sort order, legacy version (access 2000-2007)
- * @usage _intermediate_field_
- */
- public static final SortOrder GENERAL_LEGACY_SORT_ORDER =
- new SortOrder(GENERAL_SORT_ORDER_VALUE, (byte)0);
-
- /**
- * the "general" text sort order, latest version (access 2010+)
- * @usage _intermediate_field_
- */
- public static final SortOrder GENERAL_SORT_ORDER =
- new SortOrder(GENERAL_SORT_ORDER_VALUE, (byte)1);
-
- /** pattern matching textual guid strings (allows for optional surrounding
- '{' and '}') */
- 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*");
-
- /** header used to indicate unicode text compression */
- private static final byte[] TEXT_COMPRESSION_HEADER =
- { (byte)0xFF, (byte)0XFE };
-
- /** placeholder for column which is not numeric */
- private static final NumericInfo DEFAULT_NUMERIC_INFO = new NumericInfo();
-
- /** placeholder for column which is not textual */
- private static final TextInfo DEFAULT_TEXT_INFO = new TextInfo();
-
-
- /** owning table */
- private final TableImpl _table;
- /** Whether or not the column is of variable length */
- private boolean _variableLength;
- /** Whether or not the column is an autonumber column */
- private boolean _autoNumber;
- /** Data type */
- private DataType _type;
- /** Maximum column length */
- private short _columnLength;
- /** 0-based column number */
- private short _columnNumber;
- /** index of the data for this column within a list of row data */
- private int _columnIndex;
- /** display index of the data for this column */
- private int _displayIndex;
- /** Column name */
- private String _name;
- /** the offset of the fixed data in the row */
- private int _fixedDataOffset;
- /** the index of the variable length data in the var len offset table */
- private int _varLenTableIndex;
- /** information specific to numeric columns */
- private NumericInfo _numericInfo = DEFAULT_NUMERIC_INFO;
- /** information specific to text columns */
- private TextInfo _textInfo = DEFAULT_TEXT_INFO;
- /** the auto number generator for this column (if autonumber column) */
- private AutoNumberGenerator _autoNumberGenerator;
- /** additional information specific to complex columns */
- private ComplexColumnInfo<? extends ComplexValue> _complexInfo;
- /** properties for this column, if any */
- private PropertyMap _props;
-
- /**
* @usage _general_method_
*/
- public Column() {
- this(null);
- }
-
- /**
- * @usage _advanced_method_
- */
- public Column(JetFormat format) {
- _table = null;
- }
-
- /**
- * Only used by unit tests
- */
- Column(boolean testing, TableImpl table) {
- if(!testing) {
- throw new IllegalArgumentException();
- }
- _table = table;
- }
-
- /**
- * Read a column definition in from a buffer
- * @param table owning table
- * @param buffer Buffer containing column definition
- * @param offset Offset in the buffer at which the column definition starts
- * @usage _advanced_method_
- */
- public Column(TableImpl table, ByteBuffer buffer, int offset, int displayIndex)
- throws IOException
- {
- _table = table;
- _displayIndex = displayIndex;
- if (LOG.isDebugEnabled()) {
- LOG.debug("Column def block:\n" + ByteUtil.toHexString(buffer, offset, 25));
- }
-
- byte colType = buffer.get(offset + getFormat().OFFSET_COLUMN_TYPE);
- _columnNumber = buffer.getShort(offset + getFormat().OFFSET_COLUMN_NUMBER);
- _columnLength = buffer.getShort(offset + getFormat().OFFSET_COLUMN_LENGTH);
-
- byte flags = buffer.get(offset + getFormat().OFFSET_COLUMN_FLAGS);
- _variableLength = ((flags & FIXED_LEN_FLAG_MASK) == 0);
- _autoNumber = ((flags & (AUTO_NUMBER_FLAG_MASK | AUTO_NUMBER_GUID_FLAG_MASK)) != 0);
-
- try {
- _type = DataType.fromByte(colType);
- } catch(IOException e) {
- LOG.warn("Unsupported column type " + colType);
- _type = (_variableLength ? DataType.UNSUPPORTED_VARLEN :
- DataType.UNSUPPORTED_FIXEDLEN);
- setUnknownDataType(colType);
- }
-
- if (_type.getHasScalePrecision()) {
- modifyNumericInfo();
- _numericInfo._precision = buffer.get(offset +
- getFormat().OFFSET_COLUMN_PRECISION);
- _numericInfo._scale = buffer.get(offset + getFormat().OFFSET_COLUMN_SCALE);
- } else if(_type.isTextual()) {
- modifyTextInfo();
-
- // co-located w/ precision/scale
- _textInfo._sortOrder = readSortOrder(
- buffer, offset + getFormat().OFFSET_COLUMN_SORT_ORDER, getFormat());
- int cpOffset = getFormat().OFFSET_COLUMN_CODE_PAGE;
- if(cpOffset >= 0) {
- _textInfo._codePage = buffer.getShort(offset + cpOffset);
- }
-
- _textInfo._compressedUnicode = ((buffer.get(offset +
- getFormat().OFFSET_COLUMN_COMPRESSED_UNICODE) & 1) == 1);
-
- if(_type == DataType.MEMO) {
- // only memo fields can be hyperlinks
- _textInfo._hyperlink = ((flags & HYPERLINK_FLAG_MASK) != 0);
- }
- }
-
- setAutoNumberGenerator();
-
- if(_variableLength) {
- _varLenTableIndex = buffer.getShort(offset + getFormat().OFFSET_COLUMN_VARIABLE_TABLE_INDEX);
- } else {
- _fixedDataOffset = buffer.getShort(offset + getFormat().OFFSET_COLUMN_FIXED_DATA_OFFSET);
- }
-
- // load complex info
- if(_type == DataType.COMPLEX_TYPE) {
- _complexInfo = ComplexColumnInfo.create(this, buffer, offset);
- }
- }
-
- /**
- * Secondary column initialization after the table is fully loaded.
- */
- void postTableLoadInit() throws IOException {
- if(_complexInfo != null) {
- _complexInfo.postTableLoadInit();
- }
- }
+ public abstract Table getTable();
/**
* @usage _general_method_
*/
- public TableImpl getTable() {
- return _table;
- }
+ public abstract Database getDatabase();
/**
* @usage _general_method_
*/
- public DatabaseImpl getDatabase() {
- return getTable().getDatabase();
- }
-
- /**
- * @usage _advanced_method_
- */
- public JetFormat getFormat() {
- return getDatabase().getFormat();
- }
+ public abstract String getName();
/**
* @usage _advanced_method_
*/
- public PageChannel getPageChannel() {
- return getDatabase().getPageChannel();
- }
-
- /**
- * @usage _general_method_
- */
- public String getName() {
- return _name;
- }
-
- /**
- * @usage _advanced_method_
- */
- public void setName(String name) {
- _name = name;
- }
-
- /**
- * @usage _advanced_method_
- */
- public boolean isVariableLength() {
- return _variableLength;
- }
-
- /**
- * @usage _advanced_method_
- */
- public void setVariableLength(boolean variableLength) {
- _variableLength = variableLength;
- }
-
- /**
- * @usage _general_method_
- */
- public boolean isAutoNumber() {
- return _autoNumber;
- }
+ public abstract boolean isVariableLength();
/**
* @usage _general_method_
*/
- public void setAutoNumber(boolean autoNumber) {
- _autoNumber = autoNumber;
- setAutoNumberGenerator();
- }
-
- /**
- * @usage _advanced_method_
- */
- public short getColumnNumber() {
- return _columnNumber;
- }
+ public abstract boolean isAutoNumber();
/**
* @usage _advanced_method_
*/
- public void setColumnNumber(short newColumnNumber) {
- _columnNumber = newColumnNumber;
- }
+ public abstract int getColumnIndex();
/**
- * @usage _advanced_method_
- */
- public int getColumnIndex() {
- return _columnIndex;
- }
-
- /**
- * @usage _advanced_method_
- */
- public void setColumnIndex(int newColumnIndex) {
- _columnIndex = newColumnIndex;
- }
-
- /**
- * @usage _advanced_method_
- */
- public int getDisplayIndex() {
- return _displayIndex;
- }
-
- /**
- * Also sets the length and the variable length flag, inferred from the
- * type. For types with scale/precision, sets the scale and precision to
- * default values.
* @usage _general_method_
*/
- public void setType(DataType type) {
- _type = type;
- if(!type.isVariableLength()) {
- setLength((short)type.getFixedSize());
- } else if(!type.isLongValue()) {
- setLength((short)type.getDefaultSize());
- }
- setVariableLength(type.isVariableLength());
- if(type.getHasScalePrecision()) {
- setScale((byte)type.getDefaultScale());
- setPrecision((byte)type.getDefaultPrecision());
- }
- }
+ public abstract DataType getType();
/**
* @usage _general_method_
*/
- public DataType getType() {
- return _type;
- }
-
- /**
- * @usage _general_method_
- */
- public int getSQLType() throws SQLException {
- return _type.getSQLType();
- }
-
- /**
- * @usage _general_method_
- */
- public void setSQLType(int type) throws SQLException {
- setSQLType(type, 0);
- }
-
- /**
- * @usage _general_method_
- */
- public void setSQLType(int type, int lengthInUnits) throws SQLException {
- setType(DataType.fromSQLType(type, lengthInUnits));
- }
-
- /**
- * @usage _general_method_
- */
- public boolean isCompressedUnicode() {
- return _textInfo._compressedUnicode;
- }
+ public abstract int getSQLType() throws SQLException;
/**
* @usage _general_method_
*/
- public void setCompressedUnicode(boolean newCompessedUnicode) {
- modifyTextInfo();
- _textInfo._compressedUnicode = newCompessedUnicode;
- }
+ public abstract boolean isCompressedUnicode();
/**
* @usage _general_method_
*/
- public byte getPrecision() {
- return _numericInfo._precision;
- }
-
- /**
- * @usage _general_method_
- */
- public void setPrecision(byte newPrecision) {
- modifyNumericInfo();
- _numericInfo._precision = newPrecision;
- }
-
- /**
- * @usage _general_method_
- */
- public byte getScale() {
- return _numericInfo._scale;
- }
-
- /**
- * @usage _general_method_
- */
- public void setScale(byte newScale) {
- modifyNumericInfo();
- _numericInfo._scale = newScale;
- }
-
- /**
- * @usage _intermediate_method_
- */
- public SortOrder getTextSortOrder() {
- return _textInfo._sortOrder;
- }
-
- /**
- * @usage _advanced_method_
- */
- public void setTextSortOrder(SortOrder newTextSortOrder) {
- modifyTextInfo();
- _textInfo._sortOrder = newTextSortOrder;
- }
-
- /**
- * @usage _intermediate_method_
- */
- public short getTextCodePage() {
- return _textInfo._codePage;
- }
-
- /**
- * @usage _general_method_
- */
- public void setLength(short length) {
- _columnLength = length;
- }
+ public abstract byte getPrecision();
/**
* @usage _general_method_
*/
- public short getLength() {
- return _columnLength;
- }
+ public abstract byte getScale();
/**
* @usage _general_method_
*/
- public void setLengthInUnits(short unitLength) {
- setLength((short)getType().fromUnitSize(unitLength));
- }
+ public abstract short getLength();
/**
* @usage _general_method_
*/
- public short getLengthInUnits() {
- return (short)getType().toUnitSize(getLength());
- }
-
- /**
- * @usage _advanced_method_
- */
- public void setVarLenTableIndex(int idx) {
- _varLenTableIndex = idx;
- }
-
- /**
- * @usage _advanced_method_
- */
- public int getVarLenTableIndex() {
- return _varLenTableIndex;
- }
-
- /**
- * @usage _advanced_method_
- */
- public void setFixedDataOffset(int newOffset) {
- _fixedDataOffset = newOffset;
- }
-
- /**
- * @usage _advanced_method_
- */
- public int getFixedDataOffset() {
- return _fixedDataOffset;
- }
-
- Charset getCharset() {
- return getDatabase().getCharset();
- }
-
- Calendar getCalendar() {
- return getDatabase().getCalendar();
- }
+ public abstract short getLengthInUnits();
/**
* Whether or not this column is "append only" (its history is tracked by a
* separate version history column).
* @usage _general_method_
*/
- public boolean isAppendOnly() {
- return (getVersionHistoryColumn() != null);
- }
-
- /**
- * Returns the column which tracks the version history for an "append only"
- * column.
- * @usage _intermediate_method_
- */
- public Column getVersionHistoryColumn() {
- return _textInfo._versionHistoryCol;
- }
-
- /**
- * @usage _advanced_method_
- */
- public void setVersionHistoryColumn(Column versionHistoryCol) {
- modifyTextInfo();
- _textInfo._versionHistoryCol = versionHistoryCol;
- }
+ public abstract boolean isAppendOnly();
/**
* Returns whether or not this is a hyperlink column (only possible for
* columns of type MEMO).
* @usage _general_method_
*/
- public boolean isHyperlink() {
- return _textInfo._hyperlink;
- }
+ public abstract boolean isHyperlink();
/**
- * @usage _general_method_
- */
- public void setHyperlink(boolean hyperlink) {
- modifyTextInfo();
- _textInfo._hyperlink = hyperlink;
- }
-
- /**
* Returns extended functionality for "complex" columns.
* @usage _general_method_
*/
- public ComplexColumnInfo<? extends ComplexValue> getComplexInfo() {
- return _complexInfo;
- }
-
- private void setUnknownDataType(byte type) {
- // slight hack, stash the original type in the _scale
- modifyNumericInfo();
- _numericInfo._scale = type;
- }
-
- private byte getUnknownDataType() {
- // slight hack, we stashed the real type in the _scale
- return _numericInfo._scale;
- }
-
- private void setAutoNumberGenerator()
- {
- if(!_autoNumber || (_type == null)) {
- _autoNumberGenerator = null;
- return;
- }
-
- if((_autoNumberGenerator != null) &&
- (_autoNumberGenerator.getType() == _type)) {
- // keep existing
- return;
- }
-
- switch(_type) {
- case LONG:
- _autoNumberGenerator = new LongAutoNumberGenerator();
- break;
- case GUID:
- _autoNumberGenerator = new GuidAutoNumberGenerator();
- break;
- case COMPLEX_TYPE:
- _autoNumberGenerator = new ComplexTypeAutoNumberGenerator();
- break;
- default:
- LOG.warn("Unknown auto number column type " + _type);
- _autoNumberGenerator = new UnsupportedAutoNumberGenerator(_type);
- }
- }
-
- /**
- * Returns the AutoNumberGenerator for this column if this is an autonumber
- * column, {@code null} otherwise.
- * @usage _advanced_method_
- */
- public AutoNumberGenerator getAutoNumberGenerator() {
- return _autoNumberGenerator;
- }
+ public abstract ComplexColumnInfo<? extends ComplexValue> getComplexInfo();
/**
* @return the properties for this column
* @usage _general_method_
*/
- public PropertyMap getProperties() throws IOException {
- if(_props == null) {
- _props = getTable().getPropertyMaps().get(getName());
- }
- return _props;
- }
+ public abstract PropertyMap getProperties() throws IOException;
- private void modifyNumericInfo() {
- if(_numericInfo == DEFAULT_NUMERIC_INFO) {
- _numericInfo = new NumericInfo();
- }
- }
-
- private void modifyTextInfo() {
- if(_textInfo == DEFAULT_TEXT_INFO) {
- _textInfo = new TextInfo();
- }
- }
+ public abstract Object setRowValue(Object[] rowArray, Object value);
- /**
- * Checks that this column definition is valid.
- *
- * @throws IllegalArgumentException if this column definition is invalid.
- * @usage _advanced_method_
- */
- public void validate(JetFormat format) {
- if(getType() == null) {
- throw new IllegalArgumentException("must have type");
- }
- DatabaseImpl.validateIdentifierName(getName(), format.MAX_COLUMN_NAME_LENGTH,
- "column");
-
- if(getType().isUnsupported()) {
- throw new IllegalArgumentException(
- "Cannot create column with unsupported type " + getType());
- }
- if(!format.isSupportedDataType(getType())) {
- throw new IllegalArgumentException(
- "Database format " + format + " does not support type " + getType());
- }
-
- if(isVariableLength() != getType().isVariableLength()) {
- throw new IllegalArgumentException("invalid variable length setting");
- }
-
- if(!isVariableLength()) {
- if(getLength() != getType().getFixedSize()) {
- if(getLength() < getType().getFixedSize()) {
- throw new IllegalArgumentException("invalid fixed length size");
- }
- LOG.warn("Column length " + getLength() +
- " longer than expected fixed size " +
- getType().getFixedSize());
- }
- } else if(!getType().isLongValue()) {
- if(!getType().isValidSize(getLength())) {
- throw new IllegalArgumentException("var length out of range");
- }
- }
-
- if(getType().getHasScalePrecision()) {
- if(!getType().isValidScale(getScale())) {
- throw new IllegalArgumentException(
- "Scale must be from " + getType().getMinScale() + " to " +
- getType().getMaxScale() + " inclusive");
- }
- if(!getType().isValidPrecision(getPrecision())) {
- throw new IllegalArgumentException(
- "Precision must be from " + getType().getMinPrecision() + " to " +
- getType().getMaxPrecision() + " inclusive");
- }
- }
-
- if(isAutoNumber()) {
- if(!getType().mayBeAutoNumber()) {
- throw new IllegalArgumentException(
- "Auto number column must be long integer or guid");
- }
- }
-
- if(isCompressedUnicode()) {
- if(!getType().isTextual()) {
- throw new IllegalArgumentException(
- "Only textual columns allow unicode compression (text/memo)");
- }
- }
-
- if(isHyperlink()) {
- if(getType() != DataType.MEMO) {
- throw new IllegalArgumentException(
- "Only memo columns can be hyperlinks");
- }
- }
- }
-
- public Object setRowValue(Object[] rowArray, Object value) {
- rowArray[_columnIndex] = value;
- return value;
- }
-
- public Object setRowValue(Map<String,Object> rowMap, Object value) {
- rowMap.put(_name, value);
- return value;
- }
-
- public Object getRowValue(Object[] rowArray) {
- return rowArray[_columnIndex];
- }
-
- public Object getRowValue(Map<String,?> rowMap) {
- return rowMap.get(_name);
- }
-
- /**
- * Deserialize a raw byte value for this column into an Object
- * @param data The raw byte value
- * @return The deserialized Object
- * @usage _advanced_method_
- */
- public Object read(byte[] data) throws IOException {
- return read(data, PageChannel.DEFAULT_BYTE_ORDER);
- }
+ public abstract Object setRowValue(Map<String,Object> rowMap, Object value);
- /**
- * Deserialize a raw byte value for this column into an Object
- * @param data The raw byte value
- * @param order Byte order in which the raw value is stored
- * @return The deserialized Object
- * @usage _advanced_method_
- */
- public Object read(byte[] data, ByteOrder order) throws IOException {
- ByteBuffer buffer = ByteBuffer.wrap(data);
- buffer.order(order);
- if (_type == DataType.BOOLEAN) {
- throw new IOException("Tried to read a boolean from data instead of null mask.");
- } else if (_type == DataType.BYTE) {
- return Byte.valueOf(buffer.get());
- } else if (_type == DataType.INT) {
- return Short.valueOf(buffer.getShort());
- } else if (_type == DataType.LONG) {
- return Integer.valueOf(buffer.getInt());
- } else if (_type == DataType.DOUBLE) {
- return Double.valueOf(buffer.getDouble());
- } else if (_type == DataType.FLOAT) {
- return Float.valueOf(buffer.getFloat());
- } else if (_type == DataType.SHORT_DATE_TIME) {
- return readDateValue(buffer);
- } else if (_type == DataType.BINARY) {
- return data;
- } else if (_type == DataType.TEXT) {
- return decodeTextValue(data);
- } else if (_type == DataType.MONEY) {
- return readCurrencyValue(buffer);
- } else if (_type == DataType.OLE) {
- if (data.length > 0) {
- return readLongValue(data);
- }
- return null;
- } else if (_type == DataType.MEMO) {
- if (data.length > 0) {
- return readLongStringValue(data);
- }
- return null;
- } else if (_type == DataType.NUMERIC) {
- return readNumericValue(buffer);
- } else if (_type == DataType.GUID) {
- return readGUIDValue(buffer, order);
- } else if ((_type == DataType.UNKNOWN_0D) ||
- (_type == DataType.UNKNOWN_11)) {
- // treat like "binary" data
- return data;
- } else if (_type == DataType.COMPLEX_TYPE) {
- return new ComplexValueForeignKey(this, buffer.getInt());
- } else if(_type.isUnsupported()) {
- return rawDataWrapper(data);
- } else {
- throw new IOException("Unrecognized data type: " + _type);
- }
- }
-
- /**
- * @param lvalDefinition Column value that points to an LVAL record
- * @return The LVAL data
- */
- private byte[] readLongValue(byte[] lvalDefinition)
- throws IOException
- {
- ByteBuffer def = ByteBuffer.wrap(lvalDefinition)
- .order(PageChannel.DEFAULT_BYTE_ORDER);
- int lengthWithFlags = def.getInt();
- int length = lengthWithFlags & (~LONG_VALUE_TYPE_MASK);
-
- byte[] rtn = new byte[length];
- byte type = (byte)((lengthWithFlags & LONG_VALUE_TYPE_MASK) >>> 24);
-
- if(type == LONG_VALUE_TYPE_THIS_PAGE) {
-
- // inline long value
- def.getInt(); //Skip over lval_dp
- def.getInt(); //Skip over unknown
- def.get(rtn);
-
- } else {
-
- // long value on other page(s)
- if (lvalDefinition.length != getFormat().SIZE_LONG_VALUE_DEF) {
- throw new IOException("Expected " + getFormat().SIZE_LONG_VALUE_DEF +
- " bytes in long value definition, but found " +
- lvalDefinition.length);
- }
-
- int rowNum = ByteUtil.getUnsignedByte(def);
- int pageNum = ByteUtil.get3ByteInt(def, def.position());
- ByteBuffer lvalPage = getPageChannel().createPageBuffer();
-
- switch (type) {
- case LONG_VALUE_TYPE_OTHER_PAGE:
- {
- getPageChannel().readPage(lvalPage, pageNum);
-
- short rowStart = TableImpl.findRowStart(lvalPage, rowNum, getFormat());
- short rowEnd = TableImpl.findRowEnd(lvalPage, rowNum, getFormat());
-
- if((rowEnd - rowStart) != length) {
- throw new IOException("Unexpected lval row length");
- }
-
- lvalPage.position(rowStart);
- lvalPage.get(rtn);
- }
- break;
-
- case LONG_VALUE_TYPE_OTHER_PAGES:
-
- ByteBuffer rtnBuf = ByteBuffer.wrap(rtn);
- int remainingLen = length;
- while(remainingLen > 0) {
- lvalPage.clear();
- getPageChannel().readPage(lvalPage, pageNum);
-
- short rowStart = TableImpl.findRowStart(lvalPage, rowNum, getFormat());
- short rowEnd = TableImpl.findRowEnd(lvalPage, rowNum, getFormat());
-
- // read next page information
- lvalPage.position(rowStart);
- rowNum = ByteUtil.getUnsignedByte(lvalPage);
- pageNum = ByteUtil.get3ByteInt(lvalPage);
-
- // update rowEnd and remainingLen based on chunkLength
- int chunkLength = (rowEnd - rowStart) - 4;
- if(chunkLength > remainingLen) {
- rowEnd = (short)(rowEnd - (chunkLength - remainingLen));
- chunkLength = remainingLen;
- }
- remainingLen -= chunkLength;
-
- lvalPage.limit(rowEnd);
- rtnBuf.put(lvalPage);
- }
-
- break;
-
- default:
- throw new IOException("Unrecognized long value type: " + type);
- }
- }
-
- return rtn;
- }
+ public abstract Object getRowValue(Object[] rowArray);
- /**
- * @param lvalDefinition Column value that points to an LVAL record
- * @return The LVAL data
- */
- private String readLongStringValue(byte[] lvalDefinition)
- throws IOException
- {
- byte[] binData = readLongValue(lvalDefinition);
- if(binData == null) {
- return null;
- }
- return decodeTextValue(binData);
- }
-
- /**
- * Decodes "Currency" values.
- *
- * @param buffer Column value that points to currency data
- * @return BigDecimal representing the monetary value
- * @throws IOException if the value cannot be parsed
- */
- private static BigDecimal readCurrencyValue(ByteBuffer buffer)
- throws IOException
- {
- if(buffer.remaining() != 8) {
- throw new IOException("Invalid money value.");
- }
-
- return new BigDecimal(BigInteger.valueOf(buffer.getLong(0)), 4);
- }
-
- /**
- * Writes "Currency" values.
- */
- private static void writeCurrencyValue(ByteBuffer buffer, Object value)
- throws IOException
- {
- Object inValue = value;
- try {
- BigDecimal decVal = toBigDecimal(value);
- inValue = decVal;
-
- // adjust scale (will cause the an ArithmeticException if number has too
- // many decimal places)
- decVal = decVal.setScale(4);
-
- // now, remove scale and convert to long (this will throw if the value is
- // too big)
- buffer.putLong(decVal.movePointRight(4).longValueExact());
- } catch(ArithmeticException e) {
- throw (IOException)
- new IOException("Currency value '" + inValue + "' out of range")
- .initCause(e);
- }
- }
-
- /**
- * Decodes a NUMERIC field.
- */
- private BigDecimal readNumericValue(ByteBuffer buffer)
- {
- boolean negate = (buffer.get() != 0);
-
- byte[] tmpArr = ByteUtil.getBytes(buffer, 16);
-
- if(buffer.order() != ByteOrder.BIG_ENDIAN) {
- fixNumericByteOrder(tmpArr);
- }
-
- BigInteger intVal = new BigInteger(tmpArr);
- if(negate) {
- intVal = intVal.negate();
- }
- return new BigDecimal(intVal, getScale());
- }
-
- /**
- * Writes a numeric value.
- */
- private void writeNumericValue(ByteBuffer buffer, Object value)
- throws IOException
- {
- Object inValue = value;
- try {
- BigDecimal decVal = toBigDecimal(value);
- inValue = decVal;
-
- boolean negative = (decVal.compareTo(BigDecimal.ZERO) < 0);
- if(negative) {
- decVal = decVal.negate();
- }
-
- // write sign byte
- buffer.put(negative ? (byte)0x80 : (byte)0);
-
- // adjust scale according to this column type (will cause the an
- // ArithmeticException if number has too many decimal places)
- decVal = decVal.setScale(getScale());
-
- // check precision
- if(decVal.precision() > getPrecision()) {
- throw new IOException(
- "Numeric value is too big for specified precision "
- + getPrecision() + ": " + decVal);
- }
-
- // convert to unscaled BigInteger, big-endian bytes
- byte[] intValBytes = decVal.unscaledValue().toByteArray();
- int maxByteLen = getType().getFixedSize() - 1;
- if(intValBytes.length > maxByteLen) {
- throw new IOException("Too many bytes for valid BigInteger?");
- }
- if(intValBytes.length < maxByteLen) {
- byte[] tmpBytes = new byte[maxByteLen];
- System.arraycopy(intValBytes, 0, tmpBytes,
- (maxByteLen - intValBytes.length),
- intValBytes.length);
- intValBytes = tmpBytes;
- }
- if(buffer.order() != ByteOrder.BIG_ENDIAN) {
- fixNumericByteOrder(intValBytes);
- }
- buffer.put(intValBytes);
- } catch(ArithmeticException e) {
- throw (IOException)
- new IOException("Numeric value '" + inValue + "' out of range")
- .initCause(e);
- }
- }
-
- /**
- * Decodes a date value.
- */
- private Date readDateValue(ByteBuffer buffer)
- {
- // seems access stores dates in the local timezone. guess you just hope
- // you read it in the same timezone in which it was written!
- long dateBits = buffer.getLong();
- long time = fromDateDouble(Double.longBitsToDouble(dateBits));
- return new DateExt(time, dateBits);
- }
-
- /**
- * Returns a java long time value converted from an access date double.
- */
- long fromDateDouble(double value)
- {
- long time = Math.round(value * MILLISECONDS_PER_DAY);
- time -= MILLIS_BETWEEN_EPOCH_AND_1900;
- time -= getFromLocalTimeZoneOffset(time);
- return time;
- }
-
- /**
- * Writes a date value.
- */
- private void writeDateValue(ByteBuffer buffer, Object value)
- {
- if(value == null) {
- buffer.putDouble(0d);
- } else if(value instanceof DateExt) {
-
- // this is a Date value previously read from readDateValue(). use the
- // original bits to store the value so we don't lose any precision
- buffer.putLong(((DateExt)value).getDateBits());
-
- } else {
-
- buffer.putDouble(toDateDouble(value));
- }
- }
-
- /**
- * Returns an access date double converted from a java Date/Calendar/Number
- * time value.
- */
- double toDateDouble(Object value)
- {
- // seems access stores dates in the local timezone. guess you just
- // hope you read it in the same timezone in which it was written!
- long time = ((value instanceof Date) ?
- ((Date)value).getTime() :
- ((value instanceof Calendar) ?
- ((Calendar)value).getTimeInMillis() :
- ((Number)value).longValue()));
- time += getToLocalTimeZoneOffset(time);
- time += MILLIS_BETWEEN_EPOCH_AND_1900;
- return time / MILLISECONDS_PER_DAY;
- }
-
- /**
- * Gets the timezone offset from UTC to local time for the given time
- * (including DST).
- */
- private long getToLocalTimeZoneOffset(long time)
- {
- Calendar c = getCalendar();
- c.setTimeInMillis(time);
- return ((long)c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET));
- }
-
- /**
- * Gets the timezone offset from local time to UTC for the given time
- * (including DST).
- */
- private long getFromLocalTimeZoneOffset(long time)
- {
- // getting from local time back to UTC is a little wonky (and not
- // guaranteed to get you back to where you started)
- Calendar c = getCalendar();
- c.setTimeInMillis(time);
- // apply the zone offset first to get us closer to the original time
- c.setTimeInMillis(time - c.get(Calendar.ZONE_OFFSET));
- return ((long)c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET));
- }
-
- /**
- * Decodes a GUID value.
- */
- private static String readGUIDValue(ByteBuffer buffer, ByteOrder order)
- {
- if(order != ByteOrder.BIG_ENDIAN) {
- byte[] tmpArr = ByteUtil.getBytes(buffer, 16);
-
- // the first 3 guid components are integer components which need to
- // respect endianness, so swap 4-byte int, 2-byte int, 2-byte int
- ByteUtil.swap4Bytes(tmpArr, 0);
- ByteUtil.swap2Bytes(tmpArr, 4);
- ByteUtil.swap2Bytes(tmpArr, 6);
- buffer = ByteBuffer.wrap(tmpArr);
- }
-
- StringBuilder sb = new StringBuilder(22);
- sb.append("{");
- sb.append(ByteUtil.toHexString(buffer, 0, 4,
- false));
- sb.append("-");
- sb.append(ByteUtil.toHexString(buffer, 4, 2,
- false));
- sb.append("-");
- sb.append(ByteUtil.toHexString(buffer, 6, 2,
- false));
- sb.append("-");
- sb.append(ByteUtil.toHexString(buffer, 8, 2,
- false));
- sb.append("-");
- sb.append(ByteUtil.toHexString(buffer, 10, 6,
- false));
- sb.append("}");
- return (sb.toString());
- }
-
- /**
- * Writes a GUID value.
- */
- private static void writeGUIDValue(ByteBuffer buffer, Object value,
- ByteOrder order)
- throws IOException
- {
- Matcher m = GUID_PATTERN.matcher(toCharSequence(value));
- if(m.matches()) {
- ByteBuffer origBuffer = null;
- byte[] tmpBuf = null;
- if(order != ByteOrder.BIG_ENDIAN) {
- // write to a temp buf so we can do some swapping below
- origBuffer = buffer;
- tmpBuf = new byte[16];
- buffer = ByteBuffer.wrap(tmpBuf);
- }
-
- ByteUtil.writeHexString(buffer, m.group(1));
- ByteUtil.writeHexString(buffer, m.group(2));
- ByteUtil.writeHexString(buffer, m.group(3));
- ByteUtil.writeHexString(buffer, m.group(4));
- ByteUtil.writeHexString(buffer, m.group(5));
-
- if(tmpBuf != null) {
- // the first 3 guid components are integer components which need to
- // respect endianness, so swap 4-byte int, 2-byte int, 2-byte int
- ByteUtil.swap4Bytes(tmpBuf, 0);
- ByteUtil.swap2Bytes(tmpBuf, 4);
- ByteUtil.swap2Bytes(tmpBuf, 6);
- origBuffer.put(tmpBuf);
- }
-
- } else {
- throw new IOException("Invalid GUID: " + value);
- }
- }
-
- /**
- * Write an LVAL column into a ByteBuffer inline if it fits, otherwise in
- * other data page(s).
- * @param value Value of the LVAL column
- * @return A buffer containing the LVAL definition and (possibly) the column
- * value (unless written to other pages)
- * @usage _advanced_method_
- */
- public ByteBuffer writeLongValue(byte[] value,
- int remainingRowLength) throws IOException
- {
- if(value.length > getType().getMaxSize()) {
- throw new IOException("value too big for column, max " +
- getType().getMaxSize() + ", got " +
- value.length);
- }
-
- // determine which type to write
- byte type = 0;
- int lvalDefLen = getFormat().SIZE_LONG_VALUE_DEF;
- if(((getFormat().SIZE_LONG_VALUE_DEF + value.length) <= remainingRowLength)
- && (value.length <= getFormat().MAX_INLINE_LONG_VALUE_SIZE)) {
- type = LONG_VALUE_TYPE_THIS_PAGE;
- lvalDefLen += value.length;
- } else if(value.length <= getFormat().MAX_LONG_VALUE_ROW_SIZE) {
- type = LONG_VALUE_TYPE_OTHER_PAGE;
- } else {
- type = LONG_VALUE_TYPE_OTHER_PAGES;
- }
-
- ByteBuffer def = getPageChannel().createBuffer(lvalDefLen);
- // take length and apply type to first byte
- int lengthWithFlags = value.length | (type << 24);
- def.putInt(lengthWithFlags);
-
- if(type == LONG_VALUE_TYPE_THIS_PAGE) {
- // write long value inline
- def.putInt(0);
- def.putInt(0); //Unknown
- def.put(value);
- } else {
-
- TempPageHolder lvalBufferH = getTable().getLongValueBuffer();
- ByteBuffer lvalPage = null;
- int firstLvalPageNum = PageChannel.INVALID_PAGE_NUMBER;
- byte firstLvalRow = 0;
-
- // write other page(s)
- switch(type) {
- case LONG_VALUE_TYPE_OTHER_PAGE:
- lvalPage = getLongValuePage(value.length, lvalBufferH);
- firstLvalPageNum = lvalBufferH.getPageNumber();
- firstLvalRow = (byte)TableImpl.addDataPageRow(lvalPage, value.length,
- getFormat(), 0);
- lvalPage.put(value);
- getPageChannel().writePage(lvalPage, firstLvalPageNum);
- break;
-
- case LONG_VALUE_TYPE_OTHER_PAGES:
-
- ByteBuffer buffer = ByteBuffer.wrap(value);
- int remainingLen = buffer.remaining();
- buffer.limit(0);
- lvalPage = getLongValuePage(getFormat().MAX_LONG_VALUE_ROW_SIZE,
- lvalBufferH);
- firstLvalPageNum = lvalBufferH.getPageNumber();
- int lvalPageNum = firstLvalPageNum;
- ByteBuffer nextLvalPage = null;
- int nextLvalPageNum = 0;
- while(remainingLen > 0) {
- lvalPage.clear();
-
- // figure out how much we will put in this page (we need 4 bytes for
- // the next page pointer)
- int chunkLength = Math.min(getFormat().MAX_LONG_VALUE_ROW_SIZE - 4,
- remainingLen);
-
- // figure out if we will need another page, and if so, allocate it
- if(chunkLength < remainingLen) {
- // force a new page to be allocated
- lvalBufferH.clear();
- nextLvalPage = getLongValuePage(
- getFormat().MAX_LONG_VALUE_ROW_SIZE, lvalBufferH);
- nextLvalPageNum = lvalBufferH.getPageNumber();
- } else {
- nextLvalPage = null;
- nextLvalPageNum = 0;
- }
-
- // add row to this page
- byte lvalRow = (byte)TableImpl.addDataPageRow(lvalPage, chunkLength + 4,
- getFormat(), 0);
-
- // write next page info (we'll always be writing into row 0 for
- // newly created pages)
- lvalPage.put((byte)0); // row number
- ByteUtil.put3ByteInt(lvalPage, nextLvalPageNum); // page number
-
- // write this page's chunk of data
- buffer.limit(buffer.limit() + chunkLength);
- lvalPage.put(buffer);
- remainingLen -= chunkLength;
-
- // write new page to database
- getPageChannel().writePage(lvalPage, lvalPageNum);
-
- if(lvalPageNum == firstLvalPageNum) {
- // save initial row info
- firstLvalRow = lvalRow;
- } else {
- // check assertion that we wrote to row 0 for all subsequent pages
- if(lvalRow != (byte)0) {
- throw new IllegalStateException("Expected row 0, but was " +
- lvalRow);
- }
- }
-
- // move to next page
- lvalPage = nextLvalPage;
- lvalPageNum = nextLvalPageNum;
- }
- break;
-
- default:
- throw new IOException("Unrecognized long value type: " + type);
- }
-
- // update def
- def.put(firstLvalRow);
- ByteUtil.put3ByteInt(def, firstLvalPageNum);
- def.putInt(0); //Unknown
-
- }
-
- def.flip();
- return def;
- }
-
- /**
- * Writes the header info for a long value page.
- */
- private void writeLongValueHeader(ByteBuffer lvalPage)
- {
- lvalPage.put(PageTypes.DATA); //Page type
- lvalPage.put((byte) 1); //Unknown
- lvalPage.putShort((short)getFormat().DATA_PAGE_INITIAL_FREE_SPACE); //Free space
- lvalPage.put((byte) 'L');
- lvalPage.put((byte) 'V');
- lvalPage.put((byte) 'A');
- lvalPage.put((byte) 'L');
- lvalPage.putInt(0); //unknown
- lvalPage.putShort((short)0); // num rows in page
- }
-
- /**
- * Returns a long value data page with space for data of the given length.
- */
- private ByteBuffer getLongValuePage(int dataLength,
- TempPageHolder lvalBufferH)
- throws IOException
- {
- ByteBuffer lvalPage = null;
- if(lvalBufferH.getPageNumber() != PageChannel.INVALID_PAGE_NUMBER) {
- lvalPage = lvalBufferH.getPage(getPageChannel());
- if(TableImpl.rowFitsOnDataPage(dataLength, lvalPage, getFormat())) {
- // the current page has space
- return lvalPage;
- }
- }
-
- // need new page
- lvalPage = lvalBufferH.setNewPage(getPageChannel());
- writeLongValueHeader(lvalPage);
- return lvalPage;
- }
-
- /**
- * Serialize an Object into a raw byte value for this column in little
- * endian order
- * @param obj Object to serialize
- * @return A buffer containing the bytes
- * @usage _advanced_method_
- */
- public ByteBuffer write(Object obj, int remainingRowLength)
- throws IOException
- {
- return write(obj, remainingRowLength, PageChannel.DEFAULT_BYTE_ORDER);
- }
-
- /**
- * Serialize an Object into a raw byte value for this column
- * @param obj Object to serialize
- * @param order Order in which to serialize
- * @return A buffer containing the bytes
- * @usage _advanced_method_
- */
- public ByteBuffer write(Object obj, int remainingRowLength, ByteOrder order)
- throws IOException
- {
- if(isRawData(obj)) {
- // just slap it right in (not for the faint of heart!)
- return ByteBuffer.wrap(((RawData)obj).getBytes());
- }
-
- if(!isVariableLength() || !getType().isVariableLength()) {
- return writeFixedLengthField(obj, order);
- }
-
- // var length column
- if(!getType().isLongValue()) {
-
- // this is an "inline" var length field
- switch(getType()) {
- case NUMERIC:
- // don't ask me why numerics are "var length" columns...
- ByteBuffer buffer = getPageChannel().createBuffer(
- getType().getFixedSize(), order);
- writeNumericValue(buffer, obj);
- buffer.flip();
- return buffer;
-
- case TEXT:
- byte[] encodedData = encodeTextValue(
- obj, 0, getLengthInUnits(), false).array();
- obj = encodedData;
- break;
-
- case BINARY:
- case UNKNOWN_0D:
- case UNSUPPORTED_VARLEN:
- // should already be "encoded"
- break;
- default:
- throw new RuntimeException("unexpected inline var length type: " +
- getType());
- }
-
- ByteBuffer buffer = ByteBuffer.wrap(toByteArray(obj));
- buffer.order(order);
- return buffer;
- }
-
- // var length, long value column
- switch(getType()) {
- case OLE:
- // should already be "encoded"
- break;
- case MEMO:
- int maxMemoChars = DataType.MEMO.toUnitSize(DataType.MEMO.getMaxSize());
- obj = encodeTextValue(obj, 0, maxMemoChars, false).array();
- break;
- default:
- throw new RuntimeException("unexpected var length, long value type: " +
- getType());
- }
-
- // create long value buffer
- return writeLongValue(toByteArray(obj), remainingRowLength);
- }
-
- /**
- * Serialize an Object into a raw byte value for this column
- * @param obj Object to serialize
- * @param order Order in which to serialize
- * @return A buffer containing the bytes
- * @usage _advanced_method_
- */
- public ByteBuffer writeFixedLengthField(Object obj, ByteOrder order)
- throws IOException
- {
- int size = getType().getFixedSize(_columnLength);
-
- // create buffer for data
- ByteBuffer buffer = getPageChannel().createBuffer(size, order);
-
- // since booleans are not written by this method, it's safe to convert any
- // incoming boolean into an integer.
- obj = booleanToInteger(obj);
-
- switch(getType()) {
- case BOOLEAN:
- //Do nothing
- break;
- case BYTE:
- buffer.put(toNumber(obj).byteValue());
- break;
- case INT:
- buffer.putShort(toNumber(obj).shortValue());
- break;
- case LONG:
- buffer.putInt(toNumber(obj).intValue());
- break;
- case MONEY:
- writeCurrencyValue(buffer, obj);
- break;
- case FLOAT:
- buffer.putFloat(toNumber(obj).floatValue());
- break;
- case DOUBLE:
- buffer.putDouble(toNumber(obj).doubleValue());
- break;
- case SHORT_DATE_TIME:
- writeDateValue(buffer, obj);
- break;
- case TEXT:
- // apparently text numeric values are also occasionally written as fixed
- // length...
- int numChars = getLengthInUnits();
- // force uncompressed encoding for fixed length text
- buffer.put(encodeTextValue(obj, numChars, numChars, true));
- break;
- case GUID:
- writeGUIDValue(buffer, obj, order);
- break;
- case NUMERIC:
- // yes, that's right, occasionally numeric values are written as fixed
- // length...
- writeNumericValue(buffer, obj);
- break;
- case BINARY:
- case UNKNOWN_0D:
- case UNKNOWN_11:
- case COMPLEX_TYPE:
- buffer.putInt(toNumber(obj).intValue());
- break;
- case UNSUPPORTED_FIXEDLEN:
- byte[] bytes = toByteArray(obj);
- if(bytes.length != getLength()) {
- throw new IOException("Invalid fixed size binary data, size "
- + getLength() + ", got " + bytes.length);
- }
- buffer.put(bytes);
- break;
- default:
- throw new IOException("Unsupported data type: " + getType());
- }
- buffer.flip();
- return buffer;
- }
-
- /**
- * Decodes a compressed or uncompressed text value.
- */
- private String decodeTextValue(byte[] data)
- throws IOException
- {
- try {
-
- // see if data is compressed. the 0xFF, 0xFE sequence indicates that
- // compression is used (sort of, see algorithm below)
- boolean isCompressed = ((data.length > 1) &&
- (data[0] == TEXT_COMPRESSION_HEADER[0]) &&
- (data[1] == TEXT_COMPRESSION_HEADER[1]));
-
- if(isCompressed) {
-
- Expand expander = new Expand();
-
- // this is a whacky compression combo that switches back and forth
- // between compressed/uncompressed using a 0x00 byte (starting in
- // compressed mode)
- StringBuilder textBuf = new StringBuilder(data.length);
- // start after two bytes indicating compression use
- int dataStart = TEXT_COMPRESSION_HEADER.length;
- int dataEnd = dataStart;
- boolean inCompressedMode = true;
- while(dataEnd < data.length) {
- if(data[dataEnd] == (byte)0x00) {
-
- // handle current segment
- decodeTextSegment(data, dataStart, dataEnd, inCompressedMode,
- expander, textBuf);
- inCompressedMode = !inCompressedMode;
- ++dataEnd;
- dataStart = dataEnd;
-
- } else {
- ++dataEnd;
- }
- }
- // handle last segment
- decodeTextSegment(data, dataStart, dataEnd, inCompressedMode,
- expander, textBuf);
-
- return textBuf.toString();
-
- }
-
- return decodeUncompressedText(data, getCharset());
-
- } catch (IllegalInputException e) {
- throw (IOException)
- new IOException("Can't expand text column").initCause(e);
- } catch (EndOfInputException e) {
- throw (IOException)
- new IOException("Can't expand text column").initCause(e);
- }
- }
-
- /**
- * Decodes a segnment of a text value into the given buffer according to the
- * given status of the segment (compressed/uncompressed).
- */
- private void decodeTextSegment(byte[] data, int dataStart, int dataEnd,
- boolean inCompressedMode, Expand expander,
- StringBuilder textBuf)
- throws IllegalInputException, EndOfInputException
- {
- if(dataEnd <= dataStart) {
- // no data
- return;
- }
- int dataLength = dataEnd - dataStart;
- if(inCompressedMode) {
- // handle compressed data
- byte[] tmpData = ByteUtil.copyOf(data, dataStart, dataLength);
- expander.reset();
- textBuf.append(expander.expand(tmpData));
- } else {
- // handle uncompressed data
- textBuf.append(decodeUncompressedText(data, dataStart, dataLength,
- getCharset()));
- }
- }
-
- /**
- * @param textBytes bytes of text to decode
- * @return the decoded string
- */
- private static CharBuffer decodeUncompressedText(
- byte[] textBytes, int startPos, int length, Charset charset)
- {
- return charset.decode(ByteBuffer.wrap(textBytes, startPos, length));
- }
-
- /**
- * Encodes a text value, possibly compressing.
- */
- private ByteBuffer encodeTextValue(Object obj, int minChars, int maxChars,
- boolean forceUncompressed)
- throws IOException
- {
- CharSequence text = toCharSequence(obj);
- if((text.length() > maxChars) || (text.length() < minChars)) {
- throw new IOException("Text is wrong length for " + getType() +
- " column, max " + maxChars
- + ", min " + minChars + ", got " + text.length());
- }
-
- // may only compress if column type allows it
- if(!forceUncompressed && isCompressedUnicode()) {
-
- // for now, only do very simple compression (only compress text which is
- // all ascii text)
- if(isAsciiCompressible(text)) {
-
- byte[] encodedChars = new byte[TEXT_COMPRESSION_HEADER.length +
- text.length()];
- encodedChars[0] = TEXT_COMPRESSION_HEADER[0];
- encodedChars[1] = TEXT_COMPRESSION_HEADER[1];
- for(int i = 0; i < text.length(); ++i) {
- encodedChars[i + TEXT_COMPRESSION_HEADER.length] =
- (byte)text.charAt(i);
- }
- return ByteBuffer.wrap(encodedChars);
- }
- }
-
- return encodeUncompressedText(text, getCharset());
- }
-
- /**
- * Returns {@code true} if the given text can be compressed using simple
- * ASCII encoding, {@code false} otherwise.
- */
- private static boolean isAsciiCompressible(CharSequence text) {
- // only attempt to compress > 2 chars (compressing less than 3 chars would
- // not result in a space savings due to the 2 byte compression header)
- if(text.length() <= TEXT_COMPRESSION_HEADER.length) {
- return false;
- }
- // now, see if it is all printable ASCII
- for(int i = 0; i < text.length(); ++i) {
- char c = text.charAt(i);
- if(!Compress.isAsciiCrLfOrTab(c)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Constructs a byte containing the flags for this column.
- */
- private byte getColumnBitFlags() {
- byte flags = UNKNOWN_FLAG_MASK;
- if(!isVariableLength()) {
- flags |= FIXED_LEN_FLAG_MASK;
- }
- if(isAutoNumber()) {
- flags |= getAutoNumberGenerator().getColumnFlags();
- }
- if(isHyperlink()) {
- flags |= HYPERLINK_FLAG_MASK;
- }
- return flags;
- }
-
- @Override
- public String toString() {
- StringBuilder rtn = new StringBuilder();
- rtn.append("\tName: (" + _table.getName() + ") " + _name);
- byte typeValue = _type.getValue();
- if(_type.isUnsupported()) {
- typeValue = getUnknownDataType();
- }
- rtn.append("\n\tType: 0x" + Integer.toHexString(typeValue) +
- " (" + _type + ")");
- rtn.append("\n\tNumber: " + _columnNumber);
- rtn.append("\n\tLength: " + _columnLength);
- rtn.append("\n\tVariable length: " + _variableLength);
- if(_type.isTextual()) {
- rtn.append("\n\tCompressed Unicode: " + _textInfo._compressedUnicode);
- rtn.append("\n\tText Sort order: " + _textInfo._sortOrder);
- if(_textInfo._codePage > 0) {
- rtn.append("\n\tText Code Page: " + _textInfo._codePage);
- }
- if(isAppendOnly()) {
- rtn.append("\n\tAppend only: " + isAppendOnly());
- }
- if(isHyperlink()) {
- rtn.append("\n\tHyperlink: " + isHyperlink());
- }
- }
- if(_autoNumber) {
- rtn.append("\n\tLast AutoNumber: " + _autoNumberGenerator.getLast());
- }
- if(_complexInfo != null) {
- rtn.append("\n\tComplexInfo: " + _complexInfo);
- }
- rtn.append("\n\n");
- return rtn.toString();
- }
-
- /**
- * @param textBytes bytes of text to decode
- * @param charset relevant charset
- * @return the decoded string
- * @usage _advanced_method_
- */
- public static String decodeUncompressedText(byte[] textBytes,
- Charset charset)
- {
- return decodeUncompressedText(textBytes, 0, textBytes.length, charset)
- .toString();
- }
-
- /**
- * @param text Text to encode
- * @param charset database charset
- * @return A buffer with the text encoded
- * @usage _advanced_method_
- */
- public static ByteBuffer encodeUncompressedText(CharSequence text,
- Charset charset)
- {
- CharBuffer cb = ((text instanceof CharBuffer) ?
- (CharBuffer)text : CharBuffer.wrap(text));
- return charset.encode(cb);
- }
-
-
- /**
- * Orders Columns by column number.
- * @usage _general_method_
- */
- public int compareTo(Column other) {
- if (_columnNumber > other.getColumnNumber()) {
- return 1;
- } else if (_columnNumber < other.getColumnNumber()) {
- return -1;
- } else {
- return 0;
- }
- }
-
- /**
- * @param columns A list of columns in a table definition
- * @return The number of variable length columns found in the list
- * @usage _advanced_method_
- */
- public static short countVariableLength(List<Column> columns) {
- short rtn = 0;
- for (Column col : columns) {
- if (col.isVariableLength()) {
- rtn++;
- }
- }
- return rtn;
- }
-
- /**
- * @param columns A list of columns in a table definition
- * @return The number of variable length columns which are not long values
- * found in the list
- * @usage _advanced_method_
- */
- public static short countNonLongVariableLength(List<Column> columns) {
- short rtn = 0;
- for (Column col : columns) {
- if (col.isVariableLength() && !col.getType().isLongValue()) {
- rtn++;
- }
- }
- return rtn;
- }
-
- /**
- * @return an appropriate BigDecimal representation of the given object.
- * <code>null</code> is returned as 0 and Numbers are converted
- * using their double representation.
- */
- private static BigDecimal toBigDecimal(Object value)
- {
- if(value == null) {
- return BigDecimal.ZERO;
- } else if(value instanceof BigDecimal) {
- return (BigDecimal)value;
- } else if(value instanceof BigInteger) {
- return new BigDecimal((BigInteger)value);
- } else if(value instanceof Number) {
- return new BigDecimal(((Number)value).doubleValue());
- }
- return new BigDecimal(value.toString());
- }
-
- /**
- * @return an appropriate Number representation of the given object.
- * <code>null</code> is returned as 0 and Strings are parsed as
- * Doubles.
- */
- private static Number toNumber(Object value)
- {
- if(value == null) {
- return BigDecimal.ZERO;
- } if(value instanceof Number) {
- return (Number)value;
- }
- return Double.valueOf(value.toString());
- }
-
- /**
- * @return an appropriate CharSequence representation of the given object.
- * @usage _advanced_method_
- */
- public static CharSequence toCharSequence(Object value)
- throws IOException
- {
- if(value == null) {
- return null;
- } else if(value instanceof CharSequence) {
- return (CharSequence)value;
- } else if(value instanceof Clob) {
- try {
- Clob c = (Clob)value;
- // note, start pos is 1-based
- return c.getSubString(1L, (int)c.length());
- } catch(SQLException e) {
- throw (IOException)(new IOException(e.getMessage())).initCause(e);
- }
- } else if(value instanceof Reader) {
- char[] buf = new char[8 * 1024];
- StringBuilder sout = new StringBuilder();
- Reader in = (Reader)value;
- int read = 0;
- while((read = in.read(buf)) != -1) {
- sout.append(buf, 0, read);
- }
- return sout;
- }
-
- return value.toString();
- }
-
- /**
- * @return an appropriate byte[] representation of the given object.
- * @usage _advanced_method_
- */
- public static byte[] toByteArray(Object value)
- throws IOException
- {
- if(value == null) {
- return null;
- } else if(value instanceof byte[]) {
- return (byte[])value;
- } else if(value instanceof Blob) {
- try {
- Blob b = (Blob)value;
- // note, start pos is 1-based
- return b.getBytes(1L, (int)b.length());
- } catch(SQLException e) {
- throw (IOException)(new IOException(e.getMessage())).initCause(e);
- }
- }
-
- ByteArrayOutputStream bout = new ByteArrayOutputStream();
-
- if(value instanceof InputStream) {
- byte[] buf = new byte[8 * 1024];
- InputStream in = (InputStream)value;
- int read = 0;
- while((read = in.read(buf)) != -1) {
- bout.write(buf, 0, read);
- }
- } else {
- // if all else fails, serialize it
- ObjectOutputStream oos = new ObjectOutputStream(bout);
- oos.writeObject(value);
- oos.close();
- }
-
- return bout.toByteArray();
- }
-
- /**
- * Interpret a boolean value (null == false)
- * @usage _advanced_method_
- */
- public static boolean toBooleanValue(Object obj) {
- return ((obj != null) && ((Boolean)obj).booleanValue());
- }
-
- /**
- * Swaps the bytes of the given numeric in place.
- */
- private static void fixNumericByteOrder(byte[] bytes)
- {
- // fix endianness of each 4 byte segment
- for(int i = 0; i < 4; ++i) {
- ByteUtil.swap4Bytes(bytes, i * 4);
- }
- }
-
- /**
- * Treat booleans as integers (C-style).
- */
- protected static Object booleanToInteger(Object obj) {
- if (obj instanceof Boolean) {
- obj = ((Boolean) obj) ? 1 : 0;
- }
- return obj;
- }
-
- /**
- * Returns a wrapper for raw column data that can be written without
- * understanding the data. Useful for wrapping unparseable data for
- * re-writing.
- */
- static RawData rawDataWrapper(byte[] bytes) {
- return new RawData(bytes);
- }
-
- /**
- * Returs {@code true} if the given value is "raw" column data,
- * {@code false} otherwise.
- */
- static boolean isRawData(Object value) {
- return(value instanceof RawData);
- }
-
- /**
- * Writes the column definitions into a table definition buffer.
- * @param buffer Buffer to write to
- * @param columns List of Columns to write definitions for
- */
- protected static void writeDefinitions(
- TableCreator creator, ByteBuffer buffer)
- throws IOException
- {
- List<Column> columns = creator.getColumns();
- short columnNumber = (short) 0;
- short fixedOffset = (short) 0;
- short variableOffset = (short) 0;
- // we specifically put the "long variable" values after the normal
- // variable length values so that we have a better chance of fitting it
- // all (because "long variable" values can go in separate pages)
- short longVariableOffset = countNonLongVariableLength(columns);
- for (Column col : columns) {
- // record this for later use when writing indexes
- col.setColumnNumber(columnNumber);
-
- int position = buffer.position();
- buffer.put(col.getType().getValue());
- buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER); //constant magic number
- buffer.putShort(columnNumber); //Column Number
- if (col.isVariableLength()) {
- if(!col.getType().isLongValue()) {
- buffer.putShort(variableOffset++);
- } else {
- buffer.putShort(longVariableOffset++);
- }
- } else {
- buffer.putShort((short) 0);
- }
- buffer.putShort(columnNumber); //Column Number again
- if(col.getType().isTextual()) {
- // this will write 4 bytes (note we don't support writing dbs which
- // use the text code page)
- writeSortOrder(buffer, col.getTextSortOrder(), creator.getFormat());
- } else {
- if(col.getType().getHasScalePrecision()) {
- buffer.put(col.getPrecision()); // numeric precision
- buffer.put(col.getScale()); // numeric scale
- } else {
- buffer.put((byte) 0x00); //unused
- buffer.put((byte) 0x00); //unused
- }
- buffer.putShort((short) 0); //Unknown
- }
- buffer.put(col.getColumnBitFlags()); // misc col flags
- if (col.isCompressedUnicode()) { //Compressed
- buffer.put((byte) 1);
- } else {
- buffer.put((byte) 0);
- }
- buffer.putInt(0); //Unknown, but always 0.
- //Offset for fixed length columns
- if (col.isVariableLength()) {
- buffer.putShort((short) 0);
- } else {
- buffer.putShort(fixedOffset);
- fixedOffset += col.getType().getFixedSize(col.getLength());
- }
- if(!col.getType().isLongValue()) {
- buffer.putShort(col.getLength()); //Column length
- } else {
- buffer.putShort((short)0x0000); // unused
- }
- columnNumber++;
- if (LOG.isDebugEnabled()) {
- LOG.debug("Creating new column def block\n" + ByteUtil.toHexString(
- buffer, position, creator.getFormat().SIZE_COLUMN_DEF_BLOCK));
- }
- }
- for (Column col : columns) {
- TableImpl.writeName(buffer, col.getName(), creator.getCharset());
- }
- }
-
- /**
- * Reads the sort order info from the given buffer from the given position.
- */
- static SortOrder readSortOrder(ByteBuffer buffer, int position,
- JetFormat format)
- {
- short value = buffer.getShort(position);
- byte version = 0;
- if(format.SIZE_SORT_ORDER == 4) {
- version = buffer.get(position + 3);
- }
-
- if(value == 0) {
- // probably a file we wrote, before handling sort order
- return format.DEFAULT_SORT_ORDER;
- }
-
- if(value == GENERAL_SORT_ORDER_VALUE) {
- if(version == GENERAL_LEGACY_SORT_ORDER.getVersion()) {
- return GENERAL_LEGACY_SORT_ORDER;
- }
- if(version == GENERAL_SORT_ORDER.getVersion()) {
- return GENERAL_SORT_ORDER;
- }
- }
- return new SortOrder(value, version);
- }
-
- /**
- * Writes the sort order info to the given buffer at the current position.
- */
- private static void writeSortOrder(ByteBuffer buffer, SortOrder sortOrder,
- JetFormat format) {
- if(sortOrder == null) {
- sortOrder = format.DEFAULT_SORT_ORDER;
- }
- buffer.putShort(sortOrder.getValue());
- if(format.SIZE_SORT_ORDER == 4) {
- buffer.put((byte)0x00); // unknown
- buffer.put(sortOrder.getVersion());
- }
- }
-
- /**
- * Date subclass which stashes the original date bits, in case we attempt to
- * re-write the value (will not lose precision).
- */
- private static final class DateExt extends Date
- {
- private static final long serialVersionUID = 0L;
-
- /** cached bits of the original date value */
- private transient final long _dateBits;
-
- private DateExt(long time, long dateBits) {
- super(time);
- _dateBits = dateBits;
- }
-
- public long getDateBits() {
- return _dateBits;
- }
-
- private Object writeReplace() throws ObjectStreamException {
- // if we are going to serialize this Date, convert it back to a normal
- // Date (in case it is restored outside of the context of jackcess)
- return new Date(super.getTime());
- }
- }
-
- /**
- * Wrapper for raw column data which can be re-written.
- */
- private static class RawData implements Serializable
- {
- private static final long serialVersionUID = 0L;
-
- private final byte[] _bytes;
-
- private RawData(byte[] bytes) {
- _bytes = bytes;
- }
-
- private byte[] getBytes() {
- return _bytes;
- }
-
- @Override
- public String toString() {
- return "RawData: " + ByteUtil.toHexString(getBytes());
- }
-
- private Object writeReplace() throws ObjectStreamException {
- // if we are going to serialize this, convert it back to a normal
- // byte[] (in case it is restored outside of the context of jackcess)
- return getBytes();
- }
- }
-
- /**
- * Base class for the supported autonumber types.
- * @usage _advanced_class_
- */
- public abstract class AutoNumberGenerator
- {
- protected AutoNumberGenerator() {}
-
- /**
- * Returns the last autonumber generated by this generator. Only valid
- * after a call to {@link Table#addRow}, otherwise undefined.
- */
- public abstract Object getLast();
-
- /**
- * Returns the next autonumber for this generator.
- * <p>
- * <i>Warning, calling this externally will result in this value being
- * "lost" for the table.</i>
- */
- public abstract Object getNext(Object prevRowValue);
-
- /**
- * Returns the flags used when writing this column.
- */
- public abstract int getColumnFlags();
-
- /**
- * Returns the type of values generated by this generator.
- */
- public abstract DataType getType();
- }
-
- private final class LongAutoNumberGenerator extends AutoNumberGenerator
- {
- private LongAutoNumberGenerator() {}
-
- @Override
- public Object getLast() {
- // the table stores the last long autonumber used
- return getTable().getLastLongAutoNumber();
- }
-
- @Override
- public Object getNext(Object prevRowValue) {
- // the table stores the last long autonumber used
- return getTable().getNextLongAutoNumber();
- }
-
- @Override
- public int getColumnFlags() {
- return AUTO_NUMBER_FLAG_MASK;
- }
-
- @Override
- public DataType getType() {
- return DataType.LONG;
- }
- }
-
- private final class GuidAutoNumberGenerator extends AutoNumberGenerator
- {
- private Object _lastAutoNumber;
-
- private GuidAutoNumberGenerator() {}
-
- @Override
- public Object getLast() {
- return _lastAutoNumber;
- }
-
- @Override
- public Object getNext(Object prevRowValue) {
- // format guids consistently w/ Column.readGUIDValue()
- _lastAutoNumber = "{" + UUID.randomUUID() + "}";
- return _lastAutoNumber;
- }
-
- @Override
- public int getColumnFlags() {
- return AUTO_NUMBER_GUID_FLAG_MASK;
- }
-
- @Override
- public DataType getType() {
- return DataType.GUID;
- }
- }
-
- private final class ComplexTypeAutoNumberGenerator extends AutoNumberGenerator
- {
- private ComplexTypeAutoNumberGenerator() {}
-
- @Override
- public Object getLast() {
- // the table stores the last ComplexType autonumber used
- return getTable().getLastComplexTypeAutoNumber();
- }
-
- @Override
- public Object getNext(Object prevRowValue) {
- int nextComplexAutoNum =
- ((prevRowValue == null) ?
- // the table stores the last ComplexType autonumber used
- getTable().getNextComplexTypeAutoNumber() :
- // same value is shared across all ComplexType values in a row
- ((ComplexValueForeignKey)prevRowValue).get());
- return new ComplexValueForeignKey(Column.this, nextComplexAutoNum);
- }
-
- @Override
- public int getColumnFlags() {
- return AUTO_NUMBER_FLAG_MASK;
- }
-
- @Override
- public DataType getType() {
- return DataType.COMPLEX_TYPE;
- }
- }
-
- private final class UnsupportedAutoNumberGenerator extends AutoNumberGenerator
- {
- private final DataType _genType;
-
- private UnsupportedAutoNumberGenerator(DataType genType) {
- _genType = genType;
- }
-
- @Override
- public Object getLast() {
- return null;
- }
-
- @Override
- public Object getNext(Object prevRowValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getColumnFlags() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public DataType getType() {
- return _genType;
- }
- }
-
-
- /**
- * Information about the sort order (collation) for a textual column.
- * @usage _intermediate_class_
- */
- public static final class SortOrder
- {
- private final short _value;
- private final byte _version;
-
- public SortOrder(short value, byte version) {
- _value = value;
- _version = version;
- }
-
- public short getValue() {
- return _value;
- }
-
- public byte getVersion() {
- return _version;
- }
-
- @Override
- public int hashCode() {
- return _value;
- }
-
- @Override
- public boolean equals(Object o) {
- return ((this == o) ||
- ((o != null) && (getClass() == o.getClass()) &&
- (_value == ((SortOrder)o)._value) &&
- (_version == ((SortOrder)o)._version)));
- }
-
- @Override
- public String toString() {
- return _value + "(" + _version + ")";
- }
- }
-
- /**
- * Information specific to numeric types.
- */
- private static final class NumericInfo
- {
- /** Numeric precision */
- private byte _precision;
- /** Numeric scale */
- private byte _scale;
- }
-
- /**
- * Information specific to textual types.
- */
- private static final class TextInfo
- {
- /** whether or not they are compressed */
- private boolean _compressedUnicode;
- /** the collating sort order for a text field */
- private SortOrder _sortOrder;
- /** the code page for a text field (for certain db versions) */
- private short _codePage;
- /** complex column which tracks the version history for this "append only"
- column */
- private Column _versionHistoryCol;
- /** whether or not this is a hyperlink column (only possible for columns
- of type MEMO) */
- private boolean _hyperlink;
- }
+ public abstract Object getRowValue(Map<String,?> rowMap);
}
diff --git a/src/java/com/healthmarketscience/jackcess/ColumnBuilder.java b/src/java/com/healthmarketscience/jackcess/ColumnBuilder.java
index c09ec97..23154a1 100644
--- a/src/java/com/healthmarketscience/jackcess/ColumnBuilder.java
+++ b/src/java/com/healthmarketscience/jackcess/ColumnBuilder.java
@@ -142,7 +142,7 @@ public class ColumnBuilder {
/**
* Sets all attributes except name from the given Column template.
*/
- public ColumnBuilder setFromColumn(Column template) {
+ public ColumnBuilder setFromColumn(ColumnImpl template) {
DataType type = template.getType();
setType(type);
setLength(template.getLength());
@@ -168,8 +168,8 @@ public class ColumnBuilder {
/**
* Creates a new Column with the currently configured attributes.
*/
- public Column toColumn() {
- Column col = new Column();
+ public ColumnImpl toColumn() {
+ ColumnImpl col = new ColumnImpl();
col.setName(_name);
col.setType(_type);
if(_length != null) {
diff --git a/src/java/com/healthmarketscience/jackcess/ColumnImpl.java b/src/java/com/healthmarketscience/jackcess/ColumnImpl.java
new file mode 100644
index 0000000..cf824bb
--- /dev/null
+++ b/src/java/com/healthmarketscience/jackcess/ColumnImpl.java
@@ -0,0 +1,2393 @@
+/*
+Copyright (c) 2005 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.io.Reader;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.SQLException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.healthmarketscience.jackcess.complex.ComplexColumnInfo;
+import com.healthmarketscience.jackcess.complex.ComplexValue;
+import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
+import com.healthmarketscience.jackcess.scsu.Compress;
+import com.healthmarketscience.jackcess.scsu.EndOfInputException;
+import com.healthmarketscience.jackcess.scsu.Expand;
+import com.healthmarketscience.jackcess.scsu.IllegalInputException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Access database column definition
+ * @author Tim McCune
+ * @usage _general_class_
+ */
+public class ColumnImpl extends Column implements Comparable<ColumnImpl> {
+
+ private static final Log LOG = LogFactory.getLog(ColumnImpl.class);
+
+ /**
+ * Access stores numeric dates in days. Java stores them in milliseconds.
+ */
+ private static final double MILLISECONDS_PER_DAY =
+ (24L * 60L * 60L * 1000L);
+
+ /**
+ * Access starts counting dates at Jan 1, 1900. Java starts counting
+ * at Jan 1, 1970. This is the # of millis between them for conversion.
+ */
+ private static final long MILLIS_BETWEEN_EPOCH_AND_1900 =
+ 25569L * (long)MILLISECONDS_PER_DAY;
+
+ /**
+ * Long value (LVAL) type that indicates that the value is stored on the
+ * same page
+ */
+ private static final byte LONG_VALUE_TYPE_THIS_PAGE = (byte) 0x80;
+ /**
+ * Long value (LVAL) type that indicates that the value is stored on another
+ * page
+ */
+ private static final byte LONG_VALUE_TYPE_OTHER_PAGE = (byte) 0x40;
+ /**
+ * Long value (LVAL) type that indicates that the value is stored on
+ * multiple other pages
+ */
+ private static final byte LONG_VALUE_TYPE_OTHER_PAGES = (byte) 0x00;
+ /**
+ * Mask to apply the long length in order to get the flag bits (only the
+ * first 2 bits are type flags).
+ */
+ private static final int LONG_VALUE_TYPE_MASK = 0xC0000000;
+
+ /**
+ * mask for the fixed len bit
+ * @usage _advanced_field_
+ */
+ public static final byte FIXED_LEN_FLAG_MASK = (byte)0x01;
+
+ /**
+ * mask for the auto number bit
+ * @usage _advanced_field_
+ */
+ public static final byte AUTO_NUMBER_FLAG_MASK = (byte)0x04;
+
+ /**
+ * mask for the auto number guid bit
+ * @usage _advanced_field_
+ */
+ public static final byte AUTO_NUMBER_GUID_FLAG_MASK = (byte)0x40;
+
+ /**
+ * mask for the hyperlink bit (on memo types)
+ * @usage _advanced_field_
+ */
+ public static final byte HYPERLINK_FLAG_MASK = (byte)0x80;
+
+ /**
+ * mask for the unknown bit (possible "can be null"?)
+ * @usage _advanced_field_
+ */
+ public static final byte UNKNOWN_FLAG_MASK = (byte)0x02;
+
+ // some other flags?
+ // 0x10: replication related field (or hidden?)
+ // 0x80: hyperlink (some memo based thing)
+
+ /** the value for the "general" sort order */
+ private static final short GENERAL_SORT_ORDER_VALUE = 1033;
+
+ /**
+ * the "general" text sort order, legacy version (access 2000-2007)
+ * @usage _intermediate_field_
+ */
+ public static final SortOrder GENERAL_LEGACY_SORT_ORDER =
+ new SortOrder(GENERAL_SORT_ORDER_VALUE, (byte)0);
+
+ /**
+ * the "general" text sort order, latest version (access 2010+)
+ * @usage _intermediate_field_
+ */
+ public static final SortOrder GENERAL_SORT_ORDER =
+ new SortOrder(GENERAL_SORT_ORDER_VALUE, (byte)1);
+
+ /** pattern matching textual guid strings (allows for optional surrounding
+ '{' and '}') */
+ 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*");
+
+ /** header used to indicate unicode text compression */
+ private static final byte[] TEXT_COMPRESSION_HEADER =
+ { (byte)0xFF, (byte)0XFE };
+
+ /** placeholder for column which is not numeric */
+ private static final NumericInfo DEFAULT_NUMERIC_INFO = new NumericInfo();
+
+ /** placeholder for column which is not textual */
+ private static final TextInfo DEFAULT_TEXT_INFO = new TextInfo();
+
+
+ /** owning table */
+ private final TableImpl _table;
+ /** Whether or not the column is of variable length */
+ private boolean _variableLength;
+ /** Whether or not the column is an autonumber column */
+ private boolean _autoNumber;
+ /** Data type */
+ private DataType _type;
+ /** Maximum column length */
+ private short _columnLength;
+ /** 0-based column number */
+ private short _columnNumber;
+ /** index of the data for this column within a list of row data */
+ private int _columnIndex;
+ /** display index of the data for this column */
+ private int _displayIndex;
+ /** Column name */
+ private String _name;
+ /** the offset of the fixed data in the row */
+ private int _fixedDataOffset;
+ /** the index of the variable length data in the var len offset table */
+ private int _varLenTableIndex;
+ /** information specific to numeric columns */
+ private NumericInfo _numericInfo = DEFAULT_NUMERIC_INFO;
+ /** information specific to text columns */
+ private TextInfo _textInfo = DEFAULT_TEXT_INFO;
+ /** the auto number generator for this column (if autonumber column) */
+ private AutoNumberGenerator _autoNumberGenerator;
+ /** additional information specific to complex columns */
+ private ComplexColumnInfo<? extends ComplexValue> _complexInfo;
+ /** properties for this column, if any */
+ private PropertyMap _props;
+
+ /**
+ * @usage _general_method_
+ */
+ public ColumnImpl() {
+ this(null);
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public ColumnImpl(JetFormat format) {
+ _table = null;
+ }
+
+ /**
+ * Only used by unit tests
+ */
+ ColumnImpl(boolean testing, TableImpl table) {
+ if(!testing) {
+ throw new IllegalArgumentException();
+ }
+ _table = table;
+ }
+
+ /**
+ * Read a column definition in from a buffer
+ * @param table owning table
+ * @param buffer Buffer containing column definition
+ * @param offset Offset in the buffer at which the column definition starts
+ * @usage _advanced_method_
+ */
+ public ColumnImpl(TableImpl table, ByteBuffer buffer, int offset, int displayIndex)
+ throws IOException
+ {
+ _table = table;
+ _displayIndex = displayIndex;
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Column def block:\n" + ByteUtil.toHexString(buffer, offset, 25));
+ }
+
+ byte colType = buffer.get(offset + getFormat().OFFSET_COLUMN_TYPE);
+ _columnNumber = buffer.getShort(offset + getFormat().OFFSET_COLUMN_NUMBER);
+ _columnLength = buffer.getShort(offset + getFormat().OFFSET_COLUMN_LENGTH);
+
+ byte flags = buffer.get(offset + getFormat().OFFSET_COLUMN_FLAGS);
+ _variableLength = ((flags & FIXED_LEN_FLAG_MASK) == 0);
+ _autoNumber = ((flags & (AUTO_NUMBER_FLAG_MASK | AUTO_NUMBER_GUID_FLAG_MASK)) != 0);
+
+ try {
+ _type = DataType.fromByte(colType);
+ } catch(IOException e) {
+ LOG.warn("Unsupported column type " + colType);
+ _type = (_variableLength ? DataType.UNSUPPORTED_VARLEN :
+ DataType.UNSUPPORTED_FIXEDLEN);
+ setUnknownDataType(colType);
+ }
+
+ if (_type.getHasScalePrecision()) {
+ modifyNumericInfo();
+ _numericInfo._precision = buffer.get(offset +
+ getFormat().OFFSET_COLUMN_PRECISION);
+ _numericInfo._scale = buffer.get(offset + getFormat().OFFSET_COLUMN_SCALE);
+ } else if(_type.isTextual()) {
+ modifyTextInfo();
+
+ // co-located w/ precision/scale
+ _textInfo._sortOrder = readSortOrder(
+ buffer, offset + getFormat().OFFSET_COLUMN_SORT_ORDER, getFormat());
+ int cpOffset = getFormat().OFFSET_COLUMN_CODE_PAGE;
+ if(cpOffset >= 0) {
+ _textInfo._codePage = buffer.getShort(offset + cpOffset);
+ }
+
+ _textInfo._compressedUnicode = ((buffer.get(offset +
+ getFormat().OFFSET_COLUMN_COMPRESSED_UNICODE) & 1) == 1);
+
+ if(_type == DataType.MEMO) {
+ // only memo fields can be hyperlinks
+ _textInfo._hyperlink = ((flags & HYPERLINK_FLAG_MASK) != 0);
+ }
+ }
+
+ setAutoNumberGenerator();
+
+ if(_variableLength) {
+ _varLenTableIndex = buffer.getShort(offset + getFormat().OFFSET_COLUMN_VARIABLE_TABLE_INDEX);
+ } else {
+ _fixedDataOffset = buffer.getShort(offset + getFormat().OFFSET_COLUMN_FIXED_DATA_OFFSET);
+ }
+
+ // load complex info
+ if(_type == DataType.COMPLEX_TYPE) {
+ _complexInfo = ComplexColumnInfo.create(this, buffer, offset);
+ }
+ }
+
+ /**
+ * Secondary column initialization after the table is fully loaded.
+ */
+ void postTableLoadInit() throws IOException {
+ if(_complexInfo != null) {
+ _complexInfo.postTableLoadInit();
+ }
+ }
+
+ @Override
+ public TableImpl getTable() {
+ return _table;
+ }
+
+ @Override
+ public DatabaseImpl getDatabase() {
+ return getTable().getDatabase();
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public JetFormat getFormat() {
+ return getDatabase().getFormat();
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public PageChannel getPageChannel() {
+ return getDatabase().getPageChannel();
+ }
+
+ @Override
+ public String getName() {
+ return _name;
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public void setName(String name) {
+ _name = name;
+ }
+
+ @Override
+ public boolean isVariableLength() {
+ return _variableLength;
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public void setVariableLength(boolean variableLength) {
+ _variableLength = variableLength;
+ }
+
+ @Override
+ public boolean isAutoNumber() {
+ return _autoNumber;
+ }
+
+ /**
+ * @usage _general_method_
+ */
+ public void setAutoNumber(boolean autoNumber) {
+ _autoNumber = autoNumber;
+ setAutoNumberGenerator();
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public short getColumnNumber() {
+ return _columnNumber;
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public void setColumnNumber(short newColumnNumber) {
+ _columnNumber = newColumnNumber;
+ }
+
+ @Override
+ public int getColumnIndex() {
+ return _columnIndex;
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public void setColumnIndex(int newColumnIndex) {
+ _columnIndex = newColumnIndex;
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public int getDisplayIndex() {
+ return _displayIndex;
+ }
+
+ /**
+ * Also sets the length and the variable length flag, inferred from the
+ * type. For types with scale/precision, sets the scale and precision to
+ * default values.
+ * @usage _general_method_
+ */
+ public void setType(DataType type) {
+ _type = type;
+ if(!type.isVariableLength()) {
+ setLength((short)type.getFixedSize());
+ } else if(!type.isLongValue()) {
+ setLength((short)type.getDefaultSize());
+ }
+ setVariableLength(type.isVariableLength());
+ if(type.getHasScalePrecision()) {
+ setScale((byte)type.getDefaultScale());
+ setPrecision((byte)type.getDefaultPrecision());
+ }
+ }
+
+ @Override
+ public DataType getType() {
+ return _type;
+ }
+
+ @Override
+ public int getSQLType() throws SQLException {
+ return _type.getSQLType();
+ }
+
+ /**
+ * @usage _general_method_
+ */
+ public void setSQLType(int type) throws SQLException {
+ setSQLType(type, 0);
+ }
+
+ /**
+ * @usage _general_method_
+ */
+ public void setSQLType(int type, int lengthInUnits) throws SQLException {
+ setType(DataType.fromSQLType(type, lengthInUnits));
+ }
+
+ @Override
+ public boolean isCompressedUnicode() {
+ return _textInfo._compressedUnicode;
+ }
+
+ /**
+ * @usage _general_method_
+ */
+ public void setCompressedUnicode(boolean newCompessedUnicode) {
+ modifyTextInfo();
+ _textInfo._compressedUnicode = newCompessedUnicode;
+ }
+
+ @Override
+ public byte getPrecision() {
+ return _numericInfo._precision;
+ }
+
+ /**
+ * @usage _general_method_
+ */
+ public void setPrecision(byte newPrecision) {
+ modifyNumericInfo();
+ _numericInfo._precision = newPrecision;
+ }
+
+ @Override
+ public byte getScale() {
+ return _numericInfo._scale;
+ }
+
+ /**
+ * @usage _general_method_
+ */
+ public void setScale(byte newScale) {
+ modifyNumericInfo();
+ _numericInfo._scale = newScale;
+ }
+
+ /**
+ * @usage _intermediate_method_
+ */
+ public SortOrder getTextSortOrder() {
+ return _textInfo._sortOrder;
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public void setTextSortOrder(SortOrder newTextSortOrder) {
+ modifyTextInfo();
+ _textInfo._sortOrder = newTextSortOrder;
+ }
+
+ /**
+ * @usage _intermediate_method_
+ */
+ public short getTextCodePage() {
+ return _textInfo._codePage;
+ }
+
+ /**
+ * @usage _general_method_
+ */
+ public void setLength(short length) {
+ _columnLength = length;
+ }
+
+ @Override
+ public short getLength() {
+ return _columnLength;
+ }
+
+ /**
+ * @usage _general_method_
+ */
+ public void setLengthInUnits(short unitLength) {
+ setLength((short)getType().fromUnitSize(unitLength));
+ }
+
+ @Override
+ public short getLengthInUnits() {
+ return (short)getType().toUnitSize(getLength());
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public void setVarLenTableIndex(int idx) {
+ _varLenTableIndex = idx;
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public int getVarLenTableIndex() {
+ return _varLenTableIndex;
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public void setFixedDataOffset(int newOffset) {
+ _fixedDataOffset = newOffset;
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public int getFixedDataOffset() {
+ return _fixedDataOffset;
+ }
+
+ Charset getCharset() {
+ return getDatabase().getCharset();
+ }
+
+ Calendar getCalendar() {
+ return getDatabase().getCalendar();
+ }
+
+ @Override
+ public boolean isAppendOnly() {
+ return (getVersionHistoryColumn() != null);
+ }
+
+ /**
+ * Returns the column which tracks the version history for an "append only"
+ * column.
+ * @usage _intermediate_method_
+ */
+ public ColumnImpl getVersionHistoryColumn() {
+ return _textInfo._versionHistoryCol;
+ }
+
+ /**
+ * @usage _advanced_method_
+ */
+ public void setVersionHistoryColumn(ColumnImpl versionHistoryCol) {
+ modifyTextInfo();
+ _textInfo._versionHistoryCol = versionHistoryCol;
+ }
+
+ @Override
+ public boolean isHyperlink() {
+ return _textInfo._hyperlink;
+ }
+
+ /**
+ * @usage _general_method_
+ */
+ public void setHyperlink(boolean hyperlink) {
+ modifyTextInfo();
+ _textInfo._hyperlink = hyperlink;
+ }
+
+ @Override
+ public ComplexColumnInfo<? extends ComplexValue> getComplexInfo() {
+ return _complexInfo;
+ }
+
+ private void setUnknownDataType(byte type) {
+ // slight hack, stash the original type in the _scale
+ modifyNumericInfo();
+ _numericInfo._scale = type;
+ }
+
+ private byte getUnknownDataType() {
+ // slight hack, we stashed the real type in the _scale
+ return _numericInfo._scale;
+ }
+
+ private void setAutoNumberGenerator()
+ {
+ if(!_autoNumber || (_type == null)) {
+ _autoNumberGenerator = null;
+ return;
+ }
+
+ if((_autoNumberGenerator != null) &&
+ (_autoNumberGenerator.getType() == _type)) {
+ // keep existing
+ return;
+ }
+
+ switch(_type) {
+ case LONG:
+ _autoNumberGenerator = new LongAutoNumberGenerator();
+ break;
+ case GUID:
+ _autoNumberGenerator = new GuidAutoNumberGenerator();
+ break;
+ case COMPLEX_TYPE:
+ _autoNumberGenerator = new ComplexTypeAutoNumberGenerator();
+ break;
+ default:
+ LOG.warn("Unknown auto number column type " + _type);
+ _autoNumberGenerator = new UnsupportedAutoNumberGenerator(_type);
+ }
+ }
+
+ /**
+ * Returns the AutoNumberGenerator for this column if this is an autonumber
+ * column, {@code null} otherwise.
+ * @usage _advanced_method_
+ */
+ public AutoNumberGenerator getAutoNumberGenerator() {
+ return _autoNumberGenerator;
+ }
+
+ @Override
+ public PropertyMap getProperties() throws IOException {
+ if(_props == null) {
+ _props = getTable().getPropertyMaps().get(getName());
+ }
+ return _props;
+ }
+
+ private void modifyNumericInfo() {
+ if(_numericInfo == DEFAULT_NUMERIC_INFO) {
+ _numericInfo = new NumericInfo();
+ }
+ }
+
+ private void modifyTextInfo() {
+ if(_textInfo == DEFAULT_TEXT_INFO) {
+ _textInfo = new TextInfo();
+ }
+ }
+
+ /**
+ * Checks that this column definition is valid.
+ *
+ * @throws IllegalArgumentException if this column definition is invalid.
+ * @usage _advanced_method_
+ */
+ public void validate(JetFormat format) {
+ if(getType() == null) {
+ throw new IllegalArgumentException("must have type");
+ }
+ DatabaseImpl.validateIdentifierName(getName(), format.MAX_COLUMN_NAME_LENGTH,
+ "column");
+
+ if(getType().isUnsupported()) {
+ throw new IllegalArgumentException(
+ "Cannot create column with unsupported type " + getType());
+ }
+ if(!format.isSupportedDataType(getType())) {
+ throw new IllegalArgumentException(
+ "Database format " + format + " does not support type " + getType());
+ }
+
+ if(isVariableLength() != getType().isVariableLength()) {
+ throw new IllegalArgumentException("invalid variable length setting");
+ }
+
+ if(!isVariableLength()) {
+ if(getLength() != getType().getFixedSize()) {
+ if(getLength() < getType().getFixedSize()) {
+ throw new IllegalArgumentException("invalid fixed length size");
+ }
+ LOG.warn("Column length " + getLength() +
+ " longer than expected fixed size " +
+ getType().getFixedSize());
+ }
+ } else if(!getType().isLongValue()) {
+ if(!getType().isValidSize(getLength())) {
+ throw new IllegalArgumentException("var length out of range");
+ }
+ }
+
+ if(getType().getHasScalePrecision()) {
+ if(!getType().isValidScale(getScale())) {
+ throw new IllegalArgumentException(
+ "Scale must be from " + getType().getMinScale() + " to " +
+ getType().getMaxScale() + " inclusive");
+ }
+ if(!getType().isValidPrecision(getPrecision())) {
+ throw new IllegalArgumentException(
+ "Precision must be from " + getType().getMinPrecision() + " to " +
+ getType().getMaxPrecision() + " inclusive");
+ }
+ }
+
+ if(isAutoNumber()) {
+ if(!getType().mayBeAutoNumber()) {
+ throw new IllegalArgumentException(
+ "Auto number column must be long integer or guid");
+ }
+ }
+
+ if(isCompressedUnicode()) {
+ if(!getType().isTextual()) {
+ throw new IllegalArgumentException(
+ "Only textual columns allow unicode compression (text/memo)");
+ }
+ }
+
+ if(isHyperlink()) {
+ if(getType() != DataType.MEMO) {
+ throw new IllegalArgumentException(
+ "Only memo columns can be hyperlinks");
+ }
+ }
+ }
+
+ @Override
+ public Object setRowValue(Object[] rowArray, Object value) {
+ rowArray[_columnIndex] = value;
+ return value;
+ }
+
+ @Override
+ public Object setRowValue(Map<String,Object> rowMap, Object value) {
+ rowMap.put(_name, value);
+ return value;
+ }
+
+ @Override
+ public Object getRowValue(Object[] rowArray) {
+ return rowArray[_columnIndex];
+ }
+
+ @Override
+ public Object getRowValue(Map<String,?> rowMap) {
+ return rowMap.get(_name);
+ }
+
+ /**
+ * Deserialize a raw byte value for this column into an Object
+ * @param data The raw byte value
+ * @return The deserialized Object
+ * @usage _advanced_method_
+ */
+ public Object read(byte[] data) throws IOException {
+ return read(data, PageChannel.DEFAULT_BYTE_ORDER);
+ }
+
+ /**
+ * Deserialize a raw byte value for this column into an Object
+ * @param data The raw byte value
+ * @param order Byte order in which the raw value is stored
+ * @return The deserialized Object
+ * @usage _advanced_method_
+ */
+ public Object read(byte[] data, ByteOrder order) throws IOException {
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+ buffer.order(order);
+ if (_type == DataType.BOOLEAN) {
+ throw new IOException("Tried to read a boolean from data instead of null mask.");
+ } else if (_type == DataType.BYTE) {
+ return Byte.valueOf(buffer.get());
+ } else if (_type == DataType.INT) {
+ return Short.valueOf(buffer.getShort());
+ } else if (_type == DataType.LONG) {
+ return Integer.valueOf(buffer.getInt());
+ } else if (_type == DataType.DOUBLE) {
+ return Double.valueOf(buffer.getDouble());
+ } else if (_type == DataType.FLOAT) {
+ return Float.valueOf(buffer.getFloat());
+ } else if (_type == DataType.SHORT_DATE_TIME) {
+ return readDateValue(buffer);
+ } else if (_type == DataType.BINARY) {
+ return data;
+ } else if (_type == DataType.TEXT) {
+ return decodeTextValue(data);
+ } else if (_type == DataType.MONEY) {
+ return readCurrencyValue(buffer);
+ } else if (_type == DataType.OLE) {
+ if (data.length > 0) {
+ return readLongValue(data);
+ }
+ return null;
+ } else if (_type == DataType.MEMO) {
+ if (data.length > 0) {
+ return readLongStringValue(data);
+ }
+ return null;
+ } else if (_type == DataType.NUMERIC) {
+ return readNumericValue(buffer);
+ } else if (_type == DataType.GUID) {
+ return readGUIDValue(buffer, order);
+ } else if ((_type == DataType.UNKNOWN_0D) ||
+ (_type == DataType.UNKNOWN_11)) {
+ // treat like "binary" data
+ return data;
+ } else if (_type == DataType.COMPLEX_TYPE) {
+ return new ComplexValueForeignKey(this, buffer.getInt());
+ } else if(_type.isUnsupported()) {
+ return rawDataWrapper(data);
+ } else {
+ throw new IOException("Unrecognized data type: " + _type);
+ }
+ }
+
+ /**
+ * @param lvalDefinition Column value that points to an LVAL record
+ * @return The LVAL data
+ */
+ private byte[] readLongValue(byte[] lvalDefinition)
+ throws IOException
+ {
+ ByteBuffer def = ByteBuffer.wrap(lvalDefinition)
+ .order(PageChannel.DEFAULT_BYTE_ORDER);
+ int lengthWithFlags = def.getInt();
+ int length = lengthWithFlags & (~LONG_VALUE_TYPE_MASK);
+
+ byte[] rtn = new byte[length];
+ byte type = (byte)((lengthWithFlags & LONG_VALUE_TYPE_MASK) >>> 24);
+
+ if(type == LONG_VALUE_TYPE_THIS_PAGE) {
+
+ // inline long value
+ def.getInt(); //Skip over lval_dp
+ def.getInt(); //Skip over unknown
+ def.get(rtn);
+
+ } else {
+
+ // long value on other page(s)
+ if (lvalDefinition.length != getFormat().SIZE_LONG_VALUE_DEF) {
+ throw new IOException("Expected " + getFormat().SIZE_LONG_VALUE_DEF +
+ " bytes in long value definition, but found " +
+ lvalDefinition.length);
+ }
+
+ int rowNum = ByteUtil.getUnsignedByte(def);
+ int pageNum = ByteUtil.get3ByteInt(def, def.position());
+ ByteBuffer lvalPage = getPageChannel().createPageBuffer();
+
+ switch (type) {
+ case LONG_VALUE_TYPE_OTHER_PAGE:
+ {
+ getPageChannel().readPage(lvalPage, pageNum);
+
+ short rowStart = TableImpl.findRowStart(lvalPage, rowNum, getFormat());
+ short rowEnd = TableImpl.findRowEnd(lvalPage, rowNum, getFormat());
+
+ if((rowEnd - rowStart) != length) {
+ throw new IOException("Unexpected lval row length");
+ }
+
+ lvalPage.position(rowStart);
+ lvalPage.get(rtn);
+ }
+ break;
+
+ case LONG_VALUE_TYPE_OTHER_PAGES:
+
+ ByteBuffer rtnBuf = ByteBuffer.wrap(rtn);
+ int remainingLen = length;
+ while(remainingLen > 0) {
+ lvalPage.clear();
+ getPageChannel().readPage(lvalPage, pageNum);
+
+ short rowStart = TableImpl.findRowStart(lvalPage, rowNum, getFormat());
+ short rowEnd = TableImpl.findRowEnd(lvalPage, rowNum, getFormat());
+
+ // read next page information
+ lvalPage.position(rowStart);
+ rowNum = ByteUtil.getUnsignedByte(lvalPage);
+ pageNum = ByteUtil.get3ByteInt(lvalPage);
+
+ // update rowEnd and remainingLen based on chunkLength
+ int chunkLength = (rowEnd - rowStart) - 4;
+ if(chunkLength > remainingLen) {
+ rowEnd = (short)(rowEnd - (chunkLength - remainingLen));
+ chunkLength = remainingLen;
+ }
+ remainingLen -= chunkLength;
+
+ lvalPage.limit(rowEnd);
+ rtnBuf.put(lvalPage);
+ }
+
+ break;
+
+ default:
+ throw new IOException("Unrecognized long value type: " + type);
+ }
+ }
+
+ return rtn;
+ }
+
+ /**
+ * @param lvalDefinition Column value that points to an LVAL record
+ * @return The LVAL data
+ */
+ private String readLongStringValue(byte[] lvalDefinition)
+ throws IOException
+ {
+ byte[] binData = readLongValue(lvalDefinition);
+ if(binData == null) {
+ return null;
+ }
+ return decodeTextValue(binData);
+ }
+
+ /**
+ * Decodes "Currency" values.
+ *
+ * @param buffer Column value that points to currency data
+ * @return BigDecimal representing the monetary value
+ * @throws IOException if the value cannot be parsed
+ */
+ private static BigDecimal readCurrencyValue(ByteBuffer buffer)
+ throws IOException
+ {
+ if(buffer.remaining() != 8) {
+ throw new IOException("Invalid money value.");
+ }
+
+ return new BigDecimal(BigInteger.valueOf(buffer.getLong(0)), 4);
+ }
+
+ /**
+ * Writes "Currency" values.
+ */
+ private static void writeCurrencyValue(ByteBuffer buffer, Object value)
+ throws IOException
+ {
+ Object inValue = value;
+ try {
+ BigDecimal decVal = toBigDecimal(value);
+ inValue = decVal;
+
+ // adjust scale (will cause the an ArithmeticException if number has too
+ // many decimal places)
+ decVal = decVal.setScale(4);
+
+ // now, remove scale and convert to long (this will throw if the value is
+ // too big)
+ buffer.putLong(decVal.movePointRight(4).longValueExact());
+ } catch(ArithmeticException e) {
+ throw (IOException)
+ new IOException("Currency value '" + inValue + "' out of range")
+ .initCause(e);
+ }
+ }
+
+ /**
+ * Decodes a NUMERIC field.
+ */
+ private BigDecimal readNumericValue(ByteBuffer buffer)
+ {
+ boolean negate = (buffer.get() != 0);
+
+ byte[] tmpArr = ByteUtil.getBytes(buffer, 16);
+
+ if(buffer.order() != ByteOrder.BIG_ENDIAN) {
+ fixNumericByteOrder(tmpArr);
+ }
+
+ BigInteger intVal = new BigInteger(tmpArr);
+ if(negate) {
+ intVal = intVal.negate();
+ }
+ return new BigDecimal(intVal, getScale());
+ }
+
+ /**
+ * Writes a numeric value.
+ */
+ private void writeNumericValue(ByteBuffer buffer, Object value)
+ throws IOException
+ {
+ Object inValue = value;
+ try {
+ BigDecimal decVal = toBigDecimal(value);
+ inValue = decVal;
+
+ boolean negative = (decVal.compareTo(BigDecimal.ZERO) < 0);
+ if(negative) {
+ decVal = decVal.negate();
+ }
+
+ // write sign byte
+ buffer.put(negative ? (byte)0x80 : (byte)0);
+
+ // adjust scale according to this column type (will cause the an
+ // ArithmeticException if number has too many decimal places)
+ decVal = decVal.setScale(getScale());
+
+ // check precision
+ if(decVal.precision() > getPrecision()) {
+ throw new IOException(
+ "Numeric value is too big for specified precision "
+ + getPrecision() + ": " + decVal);
+ }
+
+ // convert to unscaled BigInteger, big-endian bytes
+ byte[] intValBytes = decVal.unscaledValue().toByteArray();
+ int maxByteLen = getType().getFixedSize() - 1;
+ if(intValBytes.length > maxByteLen) {
+ throw new IOException("Too many bytes for valid BigInteger?");
+ }
+ if(intValBytes.length < maxByteLen) {
+ byte[] tmpBytes = new byte[maxByteLen];
+ System.arraycopy(intValBytes, 0, tmpBytes,
+ (maxByteLen - intValBytes.length),
+ intValBytes.length);
+ intValBytes = tmpBytes;
+ }
+ if(buffer.order() != ByteOrder.BIG_ENDIAN) {
+ fixNumericByteOrder(intValBytes);
+ }
+ buffer.put(intValBytes);
+ } catch(ArithmeticException e) {
+ throw (IOException)
+ new IOException("Numeric value '" + inValue + "' out of range")
+ .initCause(e);
+ }
+ }
+
+ /**
+ * Decodes a date value.
+ */
+ private Date readDateValue(ByteBuffer buffer)
+ {
+ // seems access stores dates in the local timezone. guess you just hope
+ // you read it in the same timezone in which it was written!
+ long dateBits = buffer.getLong();
+ long time = fromDateDouble(Double.longBitsToDouble(dateBits));
+ return new DateExt(time, dateBits);
+ }
+
+ /**
+ * Returns a java long time value converted from an access date double.
+ */
+ long fromDateDouble(double value)
+ {
+ long time = Math.round(value * MILLISECONDS_PER_DAY);
+ time -= MILLIS_BETWEEN_EPOCH_AND_1900;
+ time -= getFromLocalTimeZoneOffset(time);
+ return time;
+ }
+
+ /**
+ * Writes a date value.
+ */
+ private void writeDateValue(ByteBuffer buffer, Object value)
+ {
+ if(value == null) {
+ buffer.putDouble(0d);
+ } else if(value instanceof DateExt) {
+
+ // this is a Date value previously read from readDateValue(). use the
+ // original bits to store the value so we don't lose any precision
+ buffer.putLong(((DateExt)value).getDateBits());
+
+ } else {
+
+ buffer.putDouble(toDateDouble(value));
+ }
+ }
+
+ /**
+ * Returns an access date double converted from a java Date/Calendar/Number
+ * time value.
+ */
+ double toDateDouble(Object value)
+ {
+ // seems access stores dates in the local timezone. guess you just
+ // hope you read it in the same timezone in which it was written!
+ long time = ((value instanceof Date) ?
+ ((Date)value).getTime() :
+ ((value instanceof Calendar) ?
+ ((Calendar)value).getTimeInMillis() :
+ ((Number)value).longValue()));
+ time += getToLocalTimeZoneOffset(time);
+ time += MILLIS_BETWEEN_EPOCH_AND_1900;
+ return time / MILLISECONDS_PER_DAY;
+ }
+
+ /**
+ * Gets the timezone offset from UTC to local time for the given time
+ * (including DST).
+ */
+ private long getToLocalTimeZoneOffset(long time)
+ {
+ Calendar c = getCalendar();
+ c.setTimeInMillis(time);
+ return ((long)c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET));
+ }
+
+ /**
+ * Gets the timezone offset from local time to UTC for the given time
+ * (including DST).
+ */
+ private long getFromLocalTimeZoneOffset(long time)
+ {
+ // getting from local time back to UTC is a little wonky (and not
+ // guaranteed to get you back to where you started)
+ Calendar c = getCalendar();
+ c.setTimeInMillis(time);
+ // apply the zone offset first to get us closer to the original time
+ c.setTimeInMillis(time - c.get(Calendar.ZONE_OFFSET));
+ return ((long)c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET));
+ }
+
+ /**
+ * Decodes a GUID value.
+ */
+ private static String readGUIDValue(ByteBuffer buffer, ByteOrder order)
+ {
+ if(order != ByteOrder.BIG_ENDIAN) {
+ byte[] tmpArr = ByteUtil.getBytes(buffer, 16);
+
+ // the first 3 guid components are integer components which need to
+ // respect endianness, so swap 4-byte int, 2-byte int, 2-byte int
+ ByteUtil.swap4Bytes(tmpArr, 0);
+ ByteUtil.swap2Bytes(tmpArr, 4);
+ ByteUtil.swap2Bytes(tmpArr, 6);
+ buffer = ByteBuffer.wrap(tmpArr);
+ }
+
+ StringBuilder sb = new StringBuilder(22);
+ sb.append("{");
+ sb.append(ByteUtil.toHexString(buffer, 0, 4,
+ false));
+ sb.append("-");
+ sb.append(ByteUtil.toHexString(buffer, 4, 2,
+ false));
+ sb.append("-");
+ sb.append(ByteUtil.toHexString(buffer, 6, 2,
+ false));
+ sb.append("-");
+ sb.append(ByteUtil.toHexString(buffer, 8, 2,
+ false));
+ sb.append("-");
+ sb.append(ByteUtil.toHexString(buffer, 10, 6,
+ false));
+ sb.append("}");
+ return (sb.toString());
+ }
+
+ /**
+ * Writes a GUID value.
+ */
+ private static void writeGUIDValue(ByteBuffer buffer, Object value,
+ ByteOrder order)
+ throws IOException
+ {
+ Matcher m = GUID_PATTERN.matcher(toCharSequence(value));
+ if(m.matches()) {
+ ByteBuffer origBuffer = null;
+ byte[] tmpBuf = null;
+ if(order != ByteOrder.BIG_ENDIAN) {
+ // write to a temp buf so we can do some swapping below
+ origBuffer = buffer;
+ tmpBuf = new byte[16];
+ buffer = ByteBuffer.wrap(tmpBuf);
+ }
+
+ ByteUtil.writeHexString(buffer, m.group(1));
+ ByteUtil.writeHexString(buffer, m.group(2));
+ ByteUtil.writeHexString(buffer, m.group(3));
+ ByteUtil.writeHexString(buffer, m.group(4));
+ ByteUtil.writeHexString(buffer, m.group(5));
+
+ if(tmpBuf != null) {
+ // the first 3 guid components are integer components which need to
+ // respect endianness, so swap 4-byte int, 2-byte int, 2-byte int
+ ByteUtil.swap4Bytes(tmpBuf, 0);
+ ByteUtil.swap2Bytes(tmpBuf, 4);
+ ByteUtil.swap2Bytes(tmpBuf, 6);
+ origBuffer.put(tmpBuf);
+ }
+
+ } else {
+ throw new IOException("Invalid GUID: " + value);
+ }
+ }
+
+ /**
+ * Write an LVAL column into a ByteBuffer inline if it fits, otherwise in
+ * other data page(s).
+ * @param value Value of the LVAL column
+ * @return A buffer containing the LVAL definition and (possibly) the column
+ * value (unless written to other pages)
+ * @usage _advanced_method_
+ */
+ public ByteBuffer writeLongValue(byte[] value,
+ int remainingRowLength) throws IOException
+ {
+ if(value.length > getType().getMaxSize()) {
+ throw new IOException("value too big for column, max " +
+ getType().getMaxSize() + ", got " +
+ value.length);
+ }
+
+ // determine which type to write
+ byte type = 0;
+ int lvalDefLen = getFormat().SIZE_LONG_VALUE_DEF;
+ if(((getFormat().SIZE_LONG_VALUE_DEF + value.length) <= remainingRowLength)
+ && (value.length <= getFormat().MAX_INLINE_LONG_VALUE_SIZE)) {
+ type = LONG_VALUE_TYPE_THIS_PAGE;
+ lvalDefLen += value.length;
+ } else if(value.length <= getFormat().MAX_LONG_VALUE_ROW_SIZE) {
+ type = LONG_VALUE_TYPE_OTHER_PAGE;
+ } else {
+ type = LONG_VALUE_TYPE_OTHER_PAGES;
+ }
+
+ ByteBuffer def = getPageChannel().createBuffer(lvalDefLen);
+ // take length and apply type to first byte
+ int lengthWithFlags = value.length | (type << 24);
+ def.putInt(lengthWithFlags);
+
+ if(type == LONG_VALUE_TYPE_THIS_PAGE) {
+ // write long value inline
+ def.putInt(0);
+ def.putInt(0); //Unknown
+ def.put(value);
+ } else {
+
+ TempPageHolder lvalBufferH = getTable().getLongValueBuffer();
+ ByteBuffer lvalPage = null;
+ int firstLvalPageNum = PageChannel.INVALID_PAGE_NUMBER;
+ byte firstLvalRow = 0;
+
+ // write other page(s)
+ switch(type) {
+ case LONG_VALUE_TYPE_OTHER_PAGE:
+ lvalPage = getLongValuePage(value.length, lvalBufferH);
+ firstLvalPageNum = lvalBufferH.getPageNumber();
+ firstLvalRow = (byte)TableImpl.addDataPageRow(lvalPage, value.length,
+ getFormat(), 0);
+ lvalPage.put(value);
+ getPageChannel().writePage(lvalPage, firstLvalPageNum);
+ break;
+
+ case LONG_VALUE_TYPE_OTHER_PAGES:
+
+ ByteBuffer buffer = ByteBuffer.wrap(value);
+ int remainingLen = buffer.remaining();
+ buffer.limit(0);
+ lvalPage = getLongValuePage(getFormat().MAX_LONG_VALUE_ROW_SIZE,
+ lvalBufferH);
+ firstLvalPageNum = lvalBufferH.getPageNumber();
+ int lvalPageNum = firstLvalPageNum;
+ ByteBuffer nextLvalPage = null;
+ int nextLvalPageNum = 0;
+ while(remainingLen > 0) {
+ lvalPage.clear();
+
+ // figure out how much we will put in this page (we need 4 bytes for
+ // the next page pointer)
+ int chunkLength = Math.min(getFormat().MAX_LONG_VALUE_ROW_SIZE - 4,
+ remainingLen);
+
+ // figure out if we will need another page, and if so, allocate it
+ if(chunkLength < remainingLen) {
+ // force a new page to be allocated
+ lvalBufferH.clear();
+ nextLvalPage = getLongValuePage(
+ getFormat().MAX_LONG_VALUE_ROW_SIZE, lvalBufferH);
+ nextLvalPageNum = lvalBufferH.getPageNumber();
+ } else {
+ nextLvalPage = null;
+ nextLvalPageNum = 0;
+ }
+
+ // add row to this page
+ byte lvalRow = (byte)TableImpl.addDataPageRow(lvalPage, chunkLength + 4,
+ getFormat(), 0);
+
+ // write next page info (we'll always be writing into row 0 for
+ // newly created pages)
+ lvalPage.put((byte)0); // row number
+ ByteUtil.put3ByteInt(lvalPage, nextLvalPageNum); // page number
+
+ // write this page's chunk of data
+ buffer.limit(buffer.limit() + chunkLength);
+ lvalPage.put(buffer);
+ remainingLen -= chunkLength;
+
+ // write new page to database
+ getPageChannel().writePage(lvalPage, lvalPageNum);
+
+ if(lvalPageNum == firstLvalPageNum) {
+ // save initial row info
+ firstLvalRow = lvalRow;
+ } else {
+ // check assertion that we wrote to row 0 for all subsequent pages
+ if(lvalRow != (byte)0) {
+ throw new IllegalStateException("Expected row 0, but was " +
+ lvalRow);
+ }
+ }
+
+ // move to next page
+ lvalPage = nextLvalPage;
+ lvalPageNum = nextLvalPageNum;
+ }
+ break;
+
+ default:
+ throw new IOException("Unrecognized long value type: " + type);
+ }
+
+ // update def
+ def.put(firstLvalRow);
+ ByteUtil.put3ByteInt(def, firstLvalPageNum);
+ def.putInt(0); //Unknown
+
+ }
+
+ def.flip();
+ return def;
+ }
+
+ /**
+ * Writes the header info for a long value page.
+ */
+ private void writeLongValueHeader(ByteBuffer lvalPage)
+ {
+ lvalPage.put(PageTypes.DATA); //Page type
+ lvalPage.put((byte) 1); //Unknown
+ lvalPage.putShort((short)getFormat().DATA_PAGE_INITIAL_FREE_SPACE); //Free space
+ lvalPage.put((byte) 'L');
+ lvalPage.put((byte) 'V');
+ lvalPage.put((byte) 'A');
+ lvalPage.put((byte) 'L');
+ lvalPage.putInt(0); //unknown
+ lvalPage.putShort((short)0); // num rows in page
+ }
+
+ /**
+ * Returns a long value data page with space for data of the given length.
+ */
+ private ByteBuffer getLongValuePage(int dataLength,
+ TempPageHolder lvalBufferH)
+ throws IOException
+ {
+ ByteBuffer lvalPage = null;
+ if(lvalBufferH.getPageNumber() != PageChannel.INVALID_PAGE_NUMBER) {
+ lvalPage = lvalBufferH.getPage(getPageChannel());
+ if(TableImpl.rowFitsOnDataPage(dataLength, lvalPage, getFormat())) {
+ // the current page has space
+ return lvalPage;
+ }
+ }
+
+ // need new page
+ lvalPage = lvalBufferH.setNewPage(getPageChannel());
+ writeLongValueHeader(lvalPage);
+ return lvalPage;
+ }
+
+ /**
+ * Serialize an Object into a raw byte value for this column in little
+ * endian order
+ * @param obj Object to serialize
+ * @return A buffer containing the bytes
+ * @usage _advanced_method_
+ */
+ public ByteBuffer write(Object obj, int remainingRowLength)
+ throws IOException
+ {
+ return write(obj, remainingRowLength, PageChannel.DEFAULT_BYTE_ORDER);
+ }
+
+ /**
+ * Serialize an Object into a raw byte value for this column
+ * @param obj Object to serialize
+ * @param order Order in which to serialize
+ * @return A buffer containing the bytes
+ * @usage _advanced_method_
+ */
+ public ByteBuffer write(Object obj, int remainingRowLength, ByteOrder order)
+ throws IOException
+ {
+ if(isRawData(obj)) {
+ // just slap it right in (not for the faint of heart!)
+ return ByteBuffer.wrap(((RawData)obj).getBytes());
+ }
+
+ if(!isVariableLength() || !getType().isVariableLength()) {
+ return writeFixedLengthField(obj, order);
+ }
+
+ // var length column
+ if(!getType().isLongValue()) {
+
+ // this is an "inline" var length field
+ switch(getType()) {
+ case NUMERIC:
+ // don't ask me why numerics are "var length" columns...
+ ByteBuffer buffer = getPageChannel().createBuffer(
+ getType().getFixedSize(), order);
+ writeNumericValue(buffer, obj);
+ buffer.flip();
+ return buffer;
+
+ case TEXT:
+ byte[] encodedData = encodeTextValue(
+ obj, 0, getLengthInUnits(), false).array();
+ obj = encodedData;
+ break;
+
+ case BINARY:
+ case UNKNOWN_0D:
+ case UNSUPPORTED_VARLEN:
+ // should already be "encoded"
+ break;
+ default:
+ throw new RuntimeException("unexpected inline var length type: " +
+ getType());
+ }
+
+ ByteBuffer buffer = ByteBuffer.wrap(toByteArray(obj));
+ buffer.order(order);
+ return buffer;
+ }
+
+ // var length, long value column
+ switch(getType()) {
+ case OLE:
+ // should already be "encoded"
+ break;
+ case MEMO:
+ int maxMemoChars = DataType.MEMO.toUnitSize(DataType.MEMO.getMaxSize());
+ obj = encodeTextValue(obj, 0, maxMemoChars, false).array();
+ break;
+ default:
+ throw new RuntimeException("unexpected var length, long value type: " +
+ getType());
+ }
+
+ // create long value buffer
+ return writeLongValue(toByteArray(obj), remainingRowLength);
+ }
+
+ /**
+ * Serialize an Object into a raw byte value for this column
+ * @param obj Object to serialize
+ * @param order Order in which to serialize
+ * @return A buffer containing the bytes
+ * @usage _advanced_method_
+ */
+ public ByteBuffer writeFixedLengthField(Object obj, ByteOrder order)
+ throws IOException
+ {
+ int size = getType().getFixedSize(_columnLength);
+
+ // create buffer for data
+ ByteBuffer buffer = getPageChannel().createBuffer(size, order);
+
+ // since booleans are not written by this method, it's safe to convert any
+ // incoming boolean into an integer.
+ obj = booleanToInteger(obj);
+
+ switch(getType()) {
+ case BOOLEAN:
+ //Do nothing
+ break;
+ case BYTE:
+ buffer.put(toNumber(obj).byteValue());
+ break;
+ case INT:
+ buffer.putShort(toNumber(obj).shortValue());
+ break;
+ case LONG:
+ buffer.putInt(toNumber(obj).intValue());
+ break;
+ case MONEY:
+ writeCurrencyValue(buffer, obj);
+ break;
+ case FLOAT:
+ buffer.putFloat(toNumber(obj).floatValue());
+ break;
+ case DOUBLE:
+ buffer.putDouble(toNumber(obj).doubleValue());
+ break;
+ case SHORT_DATE_TIME:
+ writeDateValue(buffer, obj);
+ break;
+ case TEXT:
+ // apparently text numeric values are also occasionally written as fixed
+ // length...
+ int numChars = getLengthInUnits();
+ // force uncompressed encoding for fixed length text
+ buffer.put(encodeTextValue(obj, numChars, numChars, true));
+ break;
+ case GUID:
+ writeGUIDValue(buffer, obj, order);
+ break;
+ case NUMERIC:
+ // yes, that's right, occasionally numeric values are written as fixed
+ // length...
+ writeNumericValue(buffer, obj);
+ break;
+ case BINARY:
+ case UNKNOWN_0D:
+ case UNKNOWN_11:
+ case COMPLEX_TYPE:
+ buffer.putInt(toNumber(obj).intValue());
+ break;
+ case UNSUPPORTED_FIXEDLEN:
+ byte[] bytes = toByteArray(obj);
+ if(bytes.length != getLength()) {
+ throw new IOException("Invalid fixed size binary data, size "
+ + getLength() + ", got " + bytes.length);
+ }
+ buffer.put(bytes);
+ break;
+ default:
+ throw new IOException("Unsupported data type: " + getType());
+ }
+ buffer.flip();
+ return buffer;
+ }
+
+ /**
+ * Decodes a compressed or uncompressed text value.
+ */
+ private String decodeTextValue(byte[] data)
+ throws IOException
+ {
+ try {
+
+ // see if data is compressed. the 0xFF, 0xFE sequence indicates that
+ // compression is used (sort of, see algorithm below)
+ boolean isCompressed = ((data.length > 1) &&
+ (data[0] == TEXT_COMPRESSION_HEADER[0]) &&
+ (data[1] == TEXT_COMPRESSION_HEADER[1]));
+
+ if(isCompressed) {
+
+ Expand expander = new Expand();
+
+ // this is a whacky compression combo that switches back and forth
+ // between compressed/uncompressed using a 0x00 byte (starting in
+ // compressed mode)
+ StringBuilder textBuf = new StringBuilder(data.length);
+ // start after two bytes indicating compression use
+ int dataStart = TEXT_COMPRESSION_HEADER.length;
+ int dataEnd = dataStart;
+ boolean inCompressedMode = true;
+ while(dataEnd < data.length) {
+ if(data[dataEnd] == (byte)0x00) {
+
+ // handle current segment
+ decodeTextSegment(data, dataStart, dataEnd, inCompressedMode,
+ expander, textBuf);
+ inCompressedMode = !inCompressedMode;
+ ++dataEnd;
+ dataStart = dataEnd;
+
+ } else {
+ ++dataEnd;
+ }
+ }
+ // handle last segment
+ decodeTextSegment(data, dataStart, dataEnd, inCompressedMode,
+ expander, textBuf);
+
+ return textBuf.toString();
+
+ }
+
+ return decodeUncompressedText(data, getCharset());
+
+ } catch (IllegalInputException e) {
+ throw (IOException)
+ new IOException("Can't expand text column").initCause(e);
+ } catch (EndOfInputException e) {
+ throw (IOException)
+ new IOException("Can't expand text column").initCause(e);
+ }
+ }
+
+ /**
+ * Decodes a segnment of a text value into the given buffer according to the
+ * given status of the segment (compressed/uncompressed).
+ */
+ private void decodeTextSegment(byte[] data, int dataStart, int dataEnd,
+ boolean inCompressedMode, Expand expander,
+ StringBuilder textBuf)
+ throws IllegalInputException, EndOfInputException
+ {
+ if(dataEnd <= dataStart) {
+ // no data
+ return;
+ }
+ int dataLength = dataEnd - dataStart;
+ if(inCompressedMode) {
+ // handle compressed data
+ byte[] tmpData = ByteUtil.copyOf(data, dataStart, dataLength);
+ expander.reset();
+ textBuf.append(expander.expand(tmpData));
+ } else {
+ // handle uncompressed data
+ textBuf.append(decodeUncompressedText(data, dataStart, dataLength,
+ getCharset()));
+ }
+ }
+
+ /**
+ * @param textBytes bytes of text to decode
+ * @return the decoded string
+ */
+ private static CharBuffer decodeUncompressedText(
+ byte[] textBytes, int startPos, int length, Charset charset)
+ {
+ return charset.decode(ByteBuffer.wrap(textBytes, startPos, length));
+ }
+
+ /**
+ * Encodes a text value, possibly compressing.
+ */
+ private ByteBuffer encodeTextValue(Object obj, int minChars, int maxChars,
+ boolean forceUncompressed)
+ throws IOException
+ {
+ CharSequence text = toCharSequence(obj);
+ if((text.length() > maxChars) || (text.length() < minChars)) {
+ throw new IOException("Text is wrong length for " + getType() +
+ " column, max " + maxChars
+ + ", min " + minChars + ", got " + text.length());
+ }
+
+ // may only compress if column type allows it
+ if(!forceUncompressed && isCompressedUnicode()) {
+
+ // for now, only do very simple compression (only compress text which is
+ // all ascii text)
+ if(isAsciiCompressible(text)) {
+
+ byte[] encodedChars = new byte[TEXT_COMPRESSION_HEADER.length +
+ text.length()];
+ encodedChars[0] = TEXT_COMPRESSION_HEADER[0];
+ encodedChars[1] = TEXT_COMPRESSION_HEADER[1];
+ for(int i = 0; i < text.length(); ++i) {
+ encodedChars[i + TEXT_COMPRESSION_HEADER.length] =
+ (byte)text.charAt(i);
+ }
+ return ByteBuffer.wrap(encodedChars);
+ }
+ }
+
+ return encodeUncompressedText(text, getCharset());
+ }
+
+ /**
+ * Returns {@code true} if the given text can be compressed using simple
+ * ASCII encoding, {@code false} otherwise.
+ */
+ private static boolean isAsciiCompressible(CharSequence text) {
+ // only attempt to compress > 2 chars (compressing less than 3 chars would
+ // not result in a space savings due to the 2 byte compression header)
+ if(text.length() <= TEXT_COMPRESSION_HEADER.length) {
+ return false;
+ }
+ // now, see if it is all printable ASCII
+ for(int i = 0; i < text.length(); ++i) {
+ char c = text.charAt(i);
+ if(!Compress.isAsciiCrLfOrTab(c)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Constructs a byte containing the flags for this column.
+ */
+ private byte getColumnBitFlags() {
+ byte flags = UNKNOWN_FLAG_MASK;
+ if(!isVariableLength()) {
+ flags |= FIXED_LEN_FLAG_MASK;
+ }
+ if(isAutoNumber()) {
+ flags |= getAutoNumberGenerator().getColumnFlags();
+ }
+ if(isHyperlink()) {
+ flags |= HYPERLINK_FLAG_MASK;
+ }
+ return flags;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder rtn = new StringBuilder();
+ rtn.append("\tName: (" + _table.getName() + ") " + _name);
+ byte typeValue = _type.getValue();
+ if(_type.isUnsupported()) {
+ typeValue = getUnknownDataType();
+ }
+ rtn.append("\n\tType: 0x" + Integer.toHexString(typeValue) +
+ " (" + _type + ")");
+ rtn.append("\n\tNumber: " + _columnNumber);
+ rtn.append("\n\tLength: " + _columnLength);
+ rtn.append("\n\tVariable length: " + _variableLength);
+ if(_type.isTextual()) {
+ rtn.append("\n\tCompressed Unicode: " + _textInfo._compressedUnicode);
+ rtn.append("\n\tText Sort order: " + _textInfo._sortOrder);
+ if(_textInfo._codePage > 0) {
+ rtn.append("\n\tText Code Page: " + _textInfo._codePage);
+ }
+ if(isAppendOnly()) {
+ rtn.append("\n\tAppend only: " + isAppendOnly());
+ }
+ if(isHyperlink()) {
+ rtn.append("\n\tHyperlink: " + isHyperlink());
+ }
+ }
+ if(_autoNumber) {
+ rtn.append("\n\tLast AutoNumber: " + _autoNumberGenerator.getLast());
+ }
+ if(_complexInfo != null) {
+ rtn.append("\n\tComplexInfo: " + _complexInfo);
+ }
+ rtn.append("\n\n");
+ return rtn.toString();
+ }
+
+ /**
+ * @param textBytes bytes of text to decode
+ * @param charset relevant charset
+ * @return the decoded string
+ * @usage _advanced_method_
+ */
+ public static String decodeUncompressedText(byte[] textBytes,
+ Charset charset)
+ {
+ return decodeUncompressedText(textBytes, 0, textBytes.length, charset)
+ .toString();
+ }
+
+ /**
+ * @param text Text to encode
+ * @param charset database charset
+ * @return A buffer with the text encoded
+ * @usage _advanced_method_
+ */
+ public static ByteBuffer encodeUncompressedText(CharSequence text,
+ Charset charset)
+ {
+ CharBuffer cb = ((text instanceof CharBuffer) ?
+ (CharBuffer)text : CharBuffer.wrap(text));
+ return charset.encode(cb);
+ }
+
+
+ /**
+ * Orders Columns by column number.
+ * @usage _general_method_
+ */
+ public int compareTo(ColumnImpl other) {
+ if (_columnNumber > other.getColumnNumber()) {
+ return 1;
+ } else if (_columnNumber < other.getColumnNumber()) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * @param columns A list of columns in a table definition
+ * @return The number of variable length columns found in the list
+ * @usage _advanced_method_
+ */
+ public static short countVariableLength(List<ColumnImpl> columns) {
+ short rtn = 0;
+ for (ColumnImpl col : columns) {
+ if (col.isVariableLength()) {
+ rtn++;
+ }
+ }
+ return rtn;
+ }
+
+ /**
+ * @param columns A list of columns in a table definition
+ * @return The number of variable length columns which are not long values
+ * found in the list
+ * @usage _advanced_method_
+ */
+ public static short countNonLongVariableLength(List<ColumnImpl> columns) {
+ short rtn = 0;
+ for (ColumnImpl col : columns) {
+ if (col.isVariableLength() && !col.getType().isLongValue()) {
+ rtn++;
+ }
+ }
+ return rtn;
+ }
+
+ /**
+ * @return an appropriate BigDecimal representation of the given object.
+ * <code>null</code> is returned as 0 and Numbers are converted
+ * using their double representation.
+ */
+ private static BigDecimal toBigDecimal(Object value)
+ {
+ if(value == null) {
+ return BigDecimal.ZERO;
+ } else if(value instanceof BigDecimal) {
+ return (BigDecimal)value;
+ } else if(value instanceof BigInteger) {
+ return new BigDecimal((BigInteger)value);
+ } else if(value instanceof Number) {
+ return new BigDecimal(((Number)value).doubleValue());
+ }
+ return new BigDecimal(value.toString());
+ }
+
+ /**
+ * @return an appropriate Number representation of the given object.
+ * <code>null</code> is returned as 0 and Strings are parsed as
+ * Doubles.
+ */
+ private static Number toNumber(Object value)
+ {
+ if(value == null) {
+ return BigDecimal.ZERO;
+ } if(value instanceof Number) {
+ return (Number)value;
+ }
+ return Double.valueOf(value.toString());
+ }
+
+ /**
+ * @return an appropriate CharSequence representation of the given object.
+ * @usage _advanced_method_
+ */
+ public static CharSequence toCharSequence(Object value)
+ throws IOException
+ {
+ if(value == null) {
+ return null;
+ } else if(value instanceof CharSequence) {
+ return (CharSequence)value;
+ } else if(value instanceof Clob) {
+ try {
+ Clob c = (Clob)value;
+ // note, start pos is 1-based
+ return c.getSubString(1L, (int)c.length());
+ } catch(SQLException e) {
+ throw (IOException)(new IOException(e.getMessage())).initCause(e);
+ }
+ } else if(value instanceof Reader) {
+ char[] buf = new char[8 * 1024];
+ StringBuilder sout = new StringBuilder();
+ Reader in = (Reader)value;
+ int read = 0;
+ while((read = in.read(buf)) != -1) {
+ sout.append(buf, 0, read);
+ }
+ return sout;
+ }
+
+ return value.toString();
+ }
+
+ /**
+ * @return an appropriate byte[] representation of the given object.
+ * @usage _advanced_method_
+ */
+ public static byte[] toByteArray(Object value)
+ throws IOException
+ {
+ if(value == null) {
+ return null;
+ } else if(value instanceof byte[]) {
+ return (byte[])value;
+ } else if(value instanceof Blob) {
+ try {
+ Blob b = (Blob)value;
+ // note, start pos is 1-based
+ return b.getBytes(1L, (int)b.length());
+ } catch(SQLException e) {
+ throw (IOException)(new IOException(e.getMessage())).initCause(e);
+ }
+ }
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+
+ if(value instanceof InputStream) {
+ byte[] buf = new byte[8 * 1024];
+ InputStream in = (InputStream)value;
+ int read = 0;
+ while((read = in.read(buf)) != -1) {
+ bout.write(buf, 0, read);
+ }
+ } else {
+ // if all else fails, serialize it
+ ObjectOutputStream oos = new ObjectOutputStream(bout);
+ oos.writeObject(value);
+ oos.close();
+ }
+
+ return bout.toByteArray();
+ }
+
+ /**
+ * Interpret a boolean value (null == false)
+ * @usage _advanced_method_
+ */
+ public static boolean toBooleanValue(Object obj) {
+ return ((obj != null) && ((Boolean)obj).booleanValue());
+ }
+
+ /**
+ * Swaps the bytes of the given numeric in place.
+ */
+ private static void fixNumericByteOrder(byte[] bytes)
+ {
+ // fix endianness of each 4 byte segment
+ for(int i = 0; i < 4; ++i) {
+ ByteUtil.swap4Bytes(bytes, i * 4);
+ }
+ }
+
+ /**
+ * Treat booleans as integers (C-style).
+ */
+ protected static Object booleanToInteger(Object obj) {
+ if (obj instanceof Boolean) {
+ obj = ((Boolean) obj) ? 1 : 0;
+ }
+ return obj;
+ }
+
+ /**
+ * Returns a wrapper for raw column data that can be written without
+ * understanding the data. Useful for wrapping unparseable data for
+ * re-writing.
+ */
+ static RawData rawDataWrapper(byte[] bytes) {
+ return new RawData(bytes);
+ }
+
+ /**
+ * Returs {@code true} if the given value is "raw" column data,
+ * {@code false} otherwise.
+ */
+ static boolean isRawData(Object value) {
+ return(value instanceof RawData);
+ }
+
+ /**
+ * Writes the column definitions into a table definition buffer.
+ * @param buffer Buffer to write to
+ * @param columns List of Columns to write definitions for
+ */
+ protected static void writeDefinitions(TableCreator creator, ByteBuffer buffer)
+ throws IOException
+ {
+ List<ColumnImpl> columns = creator.getColumns();
+ short columnNumber = (short) 0;
+ short fixedOffset = (short) 0;
+ short variableOffset = (short) 0;
+ // we specifically put the "long variable" values after the normal
+ // variable length values so that we have a better chance of fitting it
+ // all (because "long variable" values can go in separate pages)
+ short longVariableOffset = countNonLongVariableLength(columns);
+ for (ColumnImpl col : columns) {
+ // record this for later use when writing indexes
+ col.setColumnNumber(columnNumber);
+
+ int position = buffer.position();
+ buffer.put(col.getType().getValue());
+ buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER); //constant magic number
+ buffer.putShort(columnNumber); //Column Number
+ if (col.isVariableLength()) {
+ if(!col.getType().isLongValue()) {
+ buffer.putShort(variableOffset++);
+ } else {
+ buffer.putShort(longVariableOffset++);
+ }
+ } else {
+ buffer.putShort((short) 0);
+ }
+ buffer.putShort(columnNumber); //Column Number again
+ if(col.getType().isTextual()) {
+ // this will write 4 bytes (note we don't support writing dbs which
+ // use the text code page)
+ writeSortOrder(buffer, col.getTextSortOrder(), creator.getFormat());
+ } else {
+ if(col.getType().getHasScalePrecision()) {
+ buffer.put(col.getPrecision()); // numeric precision
+ buffer.put(col.getScale()); // numeric scale
+ } else {
+ buffer.put((byte) 0x00); //unused
+ buffer.put((byte) 0x00); //unused
+ }
+ buffer.putShort((short) 0); //Unknown
+ }
+ buffer.put(col.getColumnBitFlags()); // misc col flags
+ if (col.isCompressedUnicode()) { //Compressed
+ buffer.put((byte) 1);
+ } else {
+ buffer.put((byte) 0);
+ }
+ buffer.putInt(0); //Unknown, but always 0.
+ //Offset for fixed length columns
+ if (col.isVariableLength()) {
+ buffer.putShort((short) 0);
+ } else {
+ buffer.putShort(fixedOffset);
+ fixedOffset += col.getType().getFixedSize(col.getLength());
+ }
+ if(!col.getType().isLongValue()) {
+ buffer.putShort(col.getLength()); //Column length
+ } else {
+ buffer.putShort((short)0x0000); // unused
+ }
+ columnNumber++;
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Creating new column def block\n" + ByteUtil.toHexString(
+ buffer, position, creator.getFormat().SIZE_COLUMN_DEF_BLOCK));
+ }
+ }
+ for (ColumnImpl col : columns) {
+ TableImpl.writeName(buffer, col.getName(), creator.getCharset());
+ }
+ }
+
+ /**
+ * Reads the sort order info from the given buffer from the given position.
+ */
+ static SortOrder readSortOrder(ByteBuffer buffer, int position,
+ JetFormat format)
+ {
+ short value = buffer.getShort(position);
+ byte version = 0;
+ if(format.SIZE_SORT_ORDER == 4) {
+ version = buffer.get(position + 3);
+ }
+
+ if(value == 0) {
+ // probably a file we wrote, before handling sort order
+ return format.DEFAULT_SORT_ORDER;
+ }
+
+ if(value == GENERAL_SORT_ORDER_VALUE) {
+ if(version == GENERAL_LEGACY_SORT_ORDER.getVersion()) {
+ return GENERAL_LEGACY_SORT_ORDER;
+ }
+ if(version == GENERAL_SORT_ORDER.getVersion()) {
+ return GENERAL_SORT_ORDER;
+ }
+ }
+ return new SortOrder(value, version);
+ }
+
+ /**
+ * Writes the sort order info to the given buffer at the current position.
+ */
+ private static void writeSortOrder(ByteBuffer buffer, SortOrder sortOrder,
+ JetFormat format) {
+ if(sortOrder == null) {
+ sortOrder = format.DEFAULT_SORT_ORDER;
+ }
+ buffer.putShort(sortOrder.getValue());
+ if(format.SIZE_SORT_ORDER == 4) {
+ buffer.put((byte)0x00); // unknown
+ buffer.put(sortOrder.getVersion());
+ }
+ }
+
+ /**
+ * Date subclass which stashes the original date bits, in case we attempt to
+ * re-write the value (will not lose precision).
+ */
+ private static final class DateExt extends Date
+ {
+ private static final long serialVersionUID = 0L;
+
+ /** cached bits of the original date value */
+ private transient final long _dateBits;
+
+ private DateExt(long time, long dateBits) {
+ super(time);
+ _dateBits = dateBits;
+ }
+
+ public long getDateBits() {
+ return _dateBits;
+ }
+
+ private Object writeReplace() throws ObjectStreamException {
+ // if we are going to serialize this Date, convert it back to a normal
+ // Date (in case it is restored outside of the context of jackcess)
+ return new Date(super.getTime());
+ }
+ }
+
+ /**
+ * Wrapper for raw column data which can be re-written.
+ */
+ private static class RawData implements Serializable
+ {
+ private static final long serialVersionUID = 0L;
+
+ private final byte[] _bytes;
+
+ private RawData(byte[] bytes) {
+ _bytes = bytes;
+ }
+
+ private byte[] getBytes() {
+ return _bytes;
+ }
+
+ @Override
+ public String toString() {
+ return "RawData: " + ByteUtil.toHexString(getBytes());
+ }
+
+ private Object writeReplace() throws ObjectStreamException {
+ // if we are going to serialize this, convert it back to a normal
+ // byte[] (in case it is restored outside of the context of jackcess)
+ return getBytes();
+ }
+ }
+
+ /**
+ * Base class for the supported autonumber types.
+ * @usage _advanced_class_
+ */
+ public abstract class AutoNumberGenerator
+ {
+ protected AutoNumberGenerator() {}
+
+ /**
+ * Returns the last autonumber generated by this generator. Only valid
+ * after a call to {@link Table#addRow}, otherwise undefined.
+ */
+ public abstract Object getLast();
+
+ /**
+ * Returns the next autonumber for this generator.
+ * <p>
+ * <i>Warning, calling this externally will result in this value being
+ * "lost" for the table.</i>
+ */
+ public abstract Object getNext(Object prevRowValue);
+
+ /**
+ * Returns the flags used when writing this column.
+ */
+ public abstract int getColumnFlags();
+
+ /**
+ * Returns the type of values generated by this generator.
+ */
+ public abstract DataType getType();
+ }
+
+ private final class LongAutoNumberGenerator extends AutoNumberGenerator
+ {
+ private LongAutoNumberGenerator() {}
+
+ @Override
+ public Object getLast() {
+ // the table stores the last long autonumber used
+ return getTable().getLastLongAutoNumber();
+ }
+
+ @Override
+ public Object getNext(Object prevRowValue) {
+ // the table stores the last long autonumber used
+ return getTable().getNextLongAutoNumber();
+ }
+
+ @Override
+ public int getColumnFlags() {
+ return AUTO_NUMBER_FLAG_MASK;
+ }
+
+ @Override
+ public DataType getType() {
+ return DataType.LONG;
+ }
+ }
+
+ private final class GuidAutoNumberGenerator extends AutoNumberGenerator
+ {
+ private Object _lastAutoNumber;
+
+ private GuidAutoNumberGenerator() {}
+
+ @Override
+ public Object getLast() {
+ return _lastAutoNumber;
+ }
+
+ @Override
+ public Object getNext(Object prevRowValue) {
+ // format guids consistently w/ Column.readGUIDValue()
+ _lastAutoNumber = "{" + UUID.randomUUID() + "}";
+ return _lastAutoNumber;
+ }
+
+ @Override
+ public int getColumnFlags() {
+ return AUTO_NUMBER_GUID_FLAG_MASK;
+ }
+
+ @Override
+ public DataType getType() {
+ return DataType.GUID;
+ }
+ }
+
+ private final class ComplexTypeAutoNumberGenerator extends AutoNumberGenerator
+ {
+ private ComplexTypeAutoNumberGenerator() {}
+
+ @Override
+ public Object getLast() {
+ // the table stores the last ComplexType autonumber used
+ return getTable().getLastComplexTypeAutoNumber();
+ }
+
+ @Override
+ public Object getNext(Object prevRowValue) {
+ int nextComplexAutoNum =
+ ((prevRowValue == null) ?
+ // the table stores the last ComplexType autonumber used
+ getTable().getNextComplexTypeAutoNumber() :
+ // same value is shared across all ComplexType values in a row
+ ((ComplexValueForeignKey)prevRowValue).get());
+ return new ComplexValueForeignKey(ColumnImpl.this, nextComplexAutoNum);
+ }
+
+ @Override
+ public int getColumnFlags() {
+ return AUTO_NUMBER_FLAG_MASK;
+ }
+
+ @Override
+ public DataType getType() {
+ return DataType.COMPLEX_TYPE;
+ }
+ }
+
+ private final class UnsupportedAutoNumberGenerator extends AutoNumberGenerator
+ {
+ private final DataType _genType;
+
+ private UnsupportedAutoNumberGenerator(DataType genType) {
+ _genType = genType;
+ }
+
+ @Override
+ public Object getLast() {
+ return null;
+ }
+
+ @Override
+ public Object getNext(Object prevRowValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getColumnFlags() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DataType getType() {
+ return _genType;
+ }
+ }
+
+
+ /**
+ * Information about the sort order (collation) for a textual column.
+ * @usage _intermediate_class_
+ */
+ public static final class SortOrder
+ {
+ private final short _value;
+ private final byte _version;
+
+ public SortOrder(short value, byte version) {
+ _value = value;
+ _version = version;
+ }
+
+ public short getValue() {
+ return _value;
+ }
+
+ public byte getVersion() {
+ return _version;
+ }
+
+ @Override
+ public int hashCode() {
+ return _value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return ((this == o) ||
+ ((o != null) && (getClass() == o.getClass()) &&
+ (_value == ((SortOrder)o)._value) &&
+ (_version == ((SortOrder)o)._version)));
+ }
+
+ @Override
+ public String toString() {
+ return _value + "(" + _version + ")";
+ }
+ }
+
+ /**
+ * Information specific to numeric types.
+ */
+ private static final class NumericInfo
+ {
+ /** Numeric precision */
+ private byte _precision;
+ /** Numeric scale */
+ private byte _scale;
+ }
+
+ /**
+ * Information specific to textual types.
+ */
+ private static final class TextInfo
+ {
+ /** whether or not they are compressed */
+ private boolean _compressedUnicode;
+ /** the collating sort order for a text field */
+ private SortOrder _sortOrder;
+ /** the code page for a text field (for certain db versions) */
+ private short _codePage;
+ /** complex column which tracks the version history for this "append only"
+ column */
+ private ColumnImpl _versionHistoryCol;
+ /** whether or not this is a hyperlink column (only possible for columns
+ of type MEMO) */
+ private boolean _hyperlink;
+ }
+}
diff --git a/src/java/com/healthmarketscience/jackcess/Cursor.java b/src/java/com/healthmarketscience/jackcess/Cursor.java
index 2b6a9d2..fa586fd 100644
--- a/src/java/com/healthmarketscience/jackcess/Cursor.java
+++ b/src/java/com/healthmarketscience/jackcess/Cursor.java
@@ -216,8 +216,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
* desired row
* @return the matching row or {@code null} if a match could not be found.
*/
- public static Object findValue(TableImpl table, Column column,
- Column columnPattern, Object valuePattern)
+ public static Object findValue(TableImpl table, ColumnImpl column,
+ ColumnImpl columnPattern, Object valuePattern)
throws IOException
{
Cursor cursor = createCursor(table);
@@ -269,8 +269,8 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
* desired row
* @return the matching row or {@code null} if a match could not be found.
*/
- public static Object findValue(TableImpl table, IndexImpl index, Column column,
- Column columnPattern, Object valuePattern)
+ public static Object findValue(TableImpl table, IndexImpl index, ColumnImpl column,
+ ColumnImpl columnPattern, Object valuePattern)
throws IOException
{
Cursor cursor = createIndexCursor(table, index);
@@ -531,7 +531,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
* operations, the actual exception will be contained within
*/
public Iterable<Map<String, Object>> columnMatchIterable(
- Column columnPattern, Object valuePattern)
+ ColumnImpl columnPattern, Object valuePattern)
{
return columnMatchIterable(null, columnPattern, valuePattern);
}
@@ -546,7 +546,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
* operations, the actual exception will be contained within
*/
public Iterator<Map<String, Object>> columnMatchIterator(
- Column columnPattern, Object valuePattern)
+ ColumnImpl columnPattern, Object valuePattern)
{
return columnMatchIterator(null, columnPattern, valuePattern);
}
@@ -559,7 +559,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
*/
public Iterable<Map<String, Object>> columnMatchIterable(
final Collection<String> columnNames,
- final Column columnPattern, final Object valuePattern)
+ final ColumnImpl columnPattern, final Object valuePattern)
{
return new Iterable<Map<String, Object>>() {
public Iterator<Map<String, Object>> iterator() {
@@ -580,7 +580,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
* operations, the actual exception will be contained within
*/
public Iterator<Map<String, Object>> columnMatchIterator(
- Collection<String> columnNames, Column columnPattern, Object valuePattern)
+ Collection<String> columnNames, ColumnImpl columnPattern, Object valuePattern)
{
return new ColumnMatchIterator(columnNames, columnPattern, valuePattern);
}
@@ -834,7 +834,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
* @deprecated renamed to {@link #findFirstRow(Column,Object)} to be more clear
*/
@Deprecated
- public boolean findRow(Column columnPattern, Object valuePattern)
+ public boolean findRow(ColumnImpl columnPattern, Object valuePattern)
throws IOException
{
return findFirstRow(columnPattern, valuePattern);
@@ -856,7 +856,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
* @return {@code true} if a valid row was found with the given value,
* {@code false} if no row was found
*/
- public boolean findFirstRow(Column columnPattern, Object valuePattern)
+ public boolean findFirstRow(ColumnImpl columnPattern, Object valuePattern)
throws IOException
{
Position curPos = _curPos;
@@ -890,7 +890,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
* @return {@code true} if a valid row was found with the given value,
* {@code false} if no row was found
*/
- public boolean findNextRow(Column columnPattern, Object valuePattern)
+ public boolean findNextRow(ColumnImpl columnPattern, Object valuePattern)
throws IOException
{
Position curPos = _curPos;
@@ -993,7 +993,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
* @param valuePattern value which is tested for equality with the
* corresponding value in the current row
*/
- public boolean currentRowMatches(Column columnPattern, Object valuePattern)
+ public boolean currentRowMatches(ColumnImpl columnPattern, Object valuePattern)
throws IOException
{
return _columnMatcher.matches(getTable(), columnPattern.getName(),
@@ -1039,7 +1039,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
* @return {@code true} if a valid row was found with the given value,
* {@code false} if no row was found
*/
- protected boolean findNextRowImpl(Column columnPattern, Object valuePattern)
+ protected boolean findNextRowImpl(ColumnImpl columnPattern, Object valuePattern)
throws IOException
{
while(moveToNextRow()) {
@@ -1129,7 +1129,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
/**
* Returns the given column from the current row.
*/
- public Object getCurrentRowValue(Column column)
+ public Object getCurrentRowValue(ColumnImpl column)
throws IOException
{
return _table.getRowValue(_rowState, _curPos.getRowId(), column);
@@ -1140,7 +1140,7 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
* @throws IllegalStateException if the current row is not valid (at
* beginning or end of table), or deleted.
*/
- public void setCurrentRowValue(Column column, Object value)
+ public void setCurrentRowValue(ColumnImpl column, Object value)
throws IOException
{
Object[] row = new Object[_table.getColumnCount()];
@@ -1264,11 +1264,11 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
*/
private final class ColumnMatchIterator extends BaseIterator
{
- private final Column _columnPattern;
+ private final ColumnImpl _columnPattern;
private final Object _valuePattern;
private ColumnMatchIterator(Collection<String> columnNames,
- Column columnPattern, Object valuePattern)
+ ColumnImpl columnPattern, Object valuePattern)
{
super(columnNames);
_columnPattern = columnPattern;
diff --git a/src/java/com/healthmarketscience/jackcess/CursorBuilder.java b/src/java/com/healthmarketscience/jackcess/CursorBuilder.java
index df9b3c3..3a56659 100644
--- a/src/java/com/healthmarketscience/jackcess/CursorBuilder.java
+++ b/src/java/com/healthmarketscience/jackcess/CursorBuilder.java
@@ -126,9 +126,9 @@ public class CursorBuilder {
* @throws IllegalArgumentException if no index can be found on the table
* with the given columns
*/
- public CursorBuilder setIndexByColumns(Column... columns) {
+ public CursorBuilder setIndexByColumns(ColumnImpl... columns) {
List<String> colNames = new ArrayList<String>();
- for(Column col : columns) {
+ for(ColumnImpl col : columns) {
colNames.add(col.getName());
}
return setIndexByColumns(colNames);
diff --git a/src/java/com/healthmarketscience/jackcess/DatabaseImpl.java b/src/java/com/healthmarketscience/jackcess/DatabaseImpl.java
index cbb5c7f..2f257e5 100644
--- a/src/java/com/healthmarketscience/jackcess/DatabaseImpl.java
+++ b/src/java/com/healthmarketscience/jackcess/DatabaseImpl.java
@@ -322,7 +322,7 @@ public class DatabaseImpl extends Database
/** timezone to use when handling dates */
private TimeZone _timeZone;
/** language sort order to be used for textual columns */
- private Column.SortOrder _defaultSortOrder;
+ private ColumnImpl.SortOrder _defaultSortOrder;
/** default code page to be used for textual columns (in some dbs) */
private Short _defaultCodePage;
/** the ordering used for table columns */
@@ -773,7 +773,7 @@ public class DatabaseImpl extends Database
* textual columns
* @usage _intermediate_method_
*/
- public Column.SortOrder getDefaultSortOrder() throws IOException {
+ public ColumnImpl.SortOrder getDefaultSortOrder() throws IOException {
if(_defaultSortOrder == null) {
initRootPageInfo();
@@ -801,7 +801,7 @@ public class DatabaseImpl extends Database
ByteBuffer buffer = takeSharedBuffer();
try {
_pageChannel.readPage(buffer, 0);
- _defaultSortOrder = Column.readSortOrder(
+ _defaultSortOrder = ColumnImpl.readSortOrder(
buffer, _format.OFFSET_SORT_ORDER, _format);
_defaultCodePage = buffer.getShort(_format.OFFSET_CODE_PAGE);
} finally {
@@ -956,7 +956,7 @@ public class DatabaseImpl extends Database
* @param columns List of Columns in the table
* @usage _general_method_
*/
- public void createTable(String name, List<Column> columns)
+ public void createTable(String name, List<ColumnImpl> columns)
throws IOException
{
createTable(name, columns, null);
@@ -969,7 +969,7 @@ public class DatabaseImpl extends Database
* @param indexes List of IndexBuilders describing indexes for the table
* @usage _general_method_
*/
- public void createTable(String name, List<Column> columns,
+ public void createTable(String name, List<ColumnImpl> columns,
List<IndexBuilder> indexes)
throws IOException
{
@@ -1030,6 +1030,13 @@ public class DatabaseImpl extends Database
public List<Relationship> getRelationships(Table table1, Table table2)
throws IOException
{
+ return getRelationships((TableImpl)table1, (TableImpl)table2);
+ }
+
+ public List<Relationship> getRelationships(
+ TableImpl table1, TableImpl table2)
+ throws IOException
+ {
// the relationships table does not get loaded until first accessed
if(_relationships == null) {
_relationships = getSystemTable(TABLE_SYSTEM_RELATIONSHIPS);
@@ -1046,7 +1053,7 @@ public class DatabaseImpl extends Database
// we "order" the two tables given so that we will return a collection
// of relationships in the same order regardless of whether we are given
// (TableFoo, TableBar) or (TableBar, TableFoo).
- Table tmp = table1;
+ TableImpl tmp = table1;
table1 = table2;
table2 = tmp;
}
@@ -1219,7 +1226,7 @@ public class DatabaseImpl extends Database
return null;
}
- String pwd = Column.decodeUncompressedText(pwdBytes, getCharset());
+ String pwd = ColumnImpl.decodeUncompressedText(pwdBytes, getCharset());
// remove any trailing null chars
int idx = pwd.indexOf('\0');
@@ -1238,7 +1245,7 @@ public class DatabaseImpl extends Database
* given cursor and adds them to the given list.
*/
private static void collectRelationships(
- Cursor cursor, Table fromTable, Table toTable,
+ Cursor cursor, TableImpl fromTable, TableImpl toTable,
List<Relationship> relationships)
{
for(Map<String,Object> row : cursor) {
@@ -1272,9 +1279,9 @@ public class DatabaseImpl extends Database
// add column info
int colIdx = (Integer)row.get(REL_COL_COLUMN_INDEX);
- Column fromCol = fromTable.getColumn(
+ ColumnImpl fromCol = fromTable.getColumn(
(String)row.get(REL_COL_FROM_COLUMN));
- Column toCol = toTable.getColumn(
+ ColumnImpl toCol = toTable.getColumn(
(String)row.get(REL_COL_TO_COLUMN));
rel.getFromColumns().set(colIdx, fromCol);
@@ -1295,10 +1302,10 @@ public class DatabaseImpl extends Database
Object[] catalogRow = new Object[_systemCatalog.getColumnCount()];
int idx = 0;
Date creationTime = new Date();
- for (Iterator<Column> iter = _systemCatalog.getColumns().iterator();
+ for (Iterator<ColumnImpl> iter = _systemCatalog.getColumns().iterator();
iter.hasNext(); idx++)
{
- Column col = iter.next();
+ ColumnImpl col = iter.next();
if (CAT_COL_ID.equals(col.getName())) {
catalogRow[idx] = Integer.valueOf(pageNumber);
} else if (CAT_COL_NAME.equals(col.getName())) {
@@ -1336,11 +1343,11 @@ public class DatabaseImpl extends Database
initNewTableSIDs();
}
- Table acEntries = getAccessControlEntries();
- Column acmCol = acEntries.getColumn(ACE_COL_ACM);
- Column inheritCol = acEntries.getColumn(ACE_COL_F_INHERITABLE);
- Column objIdCol = acEntries.getColumn(ACE_COL_OBJECT_ID);
- Column sidCol = acEntries.getColumn(ACE_COL_SID);
+ TableImpl acEntries = getAccessControlEntries();
+ ColumnImpl acmCol = acEntries.getColumn(ACE_COL_ACM);
+ ColumnImpl inheritCol = acEntries.getColumn(ACE_COL_F_INHERITABLE);
+ ColumnImpl objIdCol = acEntries.getColumn(ACE_COL_OBJECT_ID);
+ ColumnImpl sidCol = acEntries.getColumn(ACE_COL_SID);
// construct a collection of ACE entries mimicing those of our parent, the
// "Tables" system object
@@ -1804,7 +1811,7 @@ public class DatabaseImpl extends Database
if(cur == null) {
return null;
}
- Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
+ ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
return (Integer)cur.getCurrentRowValue(idCol);
}
@@ -1948,7 +1955,7 @@ public class DatabaseImpl extends Database
if(!_systemCatalogIdCursor.moveToPreviousRow()) {
return Integer.MIN_VALUE;
}
- Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
+ ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
return (Integer)_systemCatalogIdCursor.getCurrentRowValue(idCol);
}
}
@@ -1978,7 +1985,7 @@ public class DatabaseImpl extends Database
@Override
protected Cursor findRow(Integer objectId) throws IOException
{
- Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
+ ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
return (_systemCatalogCursor.findFirstRow(idCol, objectId) ?
_systemCatalogCursor : null);
}
@@ -2024,7 +2031,7 @@ public class DatabaseImpl extends Database
@Override
protected int findMaxSyntheticId() throws IOException {
// find max id < 0
- Column idCol = _systemCatalog.getColumn(CAT_COL_ID);
+ ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
_systemCatalogCursor.reset();
int curMaxSynthId = Integer.MIN_VALUE;
while(_systemCatalogCursor.moveToNextRow()) {
diff --git a/src/java/com/healthmarketscience/jackcess/ExportFilter.java b/src/java/com/healthmarketscience/jackcess/ExportFilter.java
index f145fd5..19faed1 100644
--- a/src/java/com/healthmarketscience/jackcess/ExportFilter.java
+++ b/src/java/com/healthmarketscience/jackcess/ExportFilter.java
@@ -46,7 +46,7 @@ public interface ExportFilter {
* modified and returned
* @return the columns to use when creating the export file
*/
- public List<Column> filterColumns(List<Column> columns) throws IOException;
+ public List<ColumnImpl> filterColumns(List<ColumnImpl> columns) throws IOException;
/**
* The desired values for the row.
diff --git a/src/java/com/healthmarketscience/jackcess/ExportUtil.java b/src/java/com/healthmarketscience/jackcess/ExportUtil.java
index 7ee1c35..1cb7eb7 100644
--- a/src/java/com/healthmarketscience/jackcess/ExportUtil.java
+++ b/src/java/com/healthmarketscience/jackcess/ExportUtil.java
@@ -303,8 +303,8 @@ public class ExportUtil {
"(?:" + Pattern.quote(delimiter) + ")|(?:" +
Pattern.quote("" + quote) + ")|(?:[\n\r])");
- List<Column> origCols = cursor.getTable().getColumns();
- List<Column> columns = new ArrayList<Column>(origCols);
+ List<ColumnImpl> origCols = cursor.getTable().getColumns();
+ List<ColumnImpl> columns = new ArrayList<ColumnImpl>(origCols);
columns = filter.filterColumns(columns);
Collection<String> columnNames = null;
@@ -312,14 +312,14 @@ public class ExportUtil {
// columns have been filtered
columnNames = new HashSet<String>();
- for (Column c : columns) {
+ for (ColumnImpl c : columns) {
columnNames.add(c.getName());
}
}
// print the header row (if desired)
if (header) {
- for (Iterator<Column> iter = columns.iterator(); iter.hasNext();) {
+ for (Iterator<ColumnImpl> iter = columns.iterator(); iter.hasNext();) {
writeValue(out, iter.next().getName(), quote, needsQuotePattern);
diff --git a/src/java/com/healthmarketscience/jackcess/FKEnforcer.java b/src/java/com/healthmarketscience/jackcess/FKEnforcer.java
index 9e148a6..b05a0f4 100644
--- a/src/java/com/healthmarketscience/jackcess/FKEnforcer.java
+++ b/src/java/com/healthmarketscience/jackcess/FKEnforcer.java
@@ -45,7 +45,7 @@ final class FKEnforcer
CaseInsensitiveColumnMatcher.INSTANCE;
private final TableImpl _table;
- private final List<Column> _cols;
+ private final List<ColumnImpl> _cols;
private List<Joiner> _primaryJoinersChkUp;
private List<Joiner> _primaryJoinersChkDel;
private List<Joiner> _primaryJoinersDoUp;
@@ -56,7 +56,7 @@ final class FKEnforcer
_table = table;
// at this point, only init the index columns
- Set<Column> cols = new TreeSet<Column>();
+ Set<ColumnImpl> cols = new TreeSet<ColumnImpl>();
for(IndexImpl idx : _table.getIndexes()) {
IndexImpl.ForeignKeyReference ref = idx.getReference();
if(ref != null) {
@@ -68,8 +68,8 @@ final class FKEnforcer
}
}
_cols = !cols.isEmpty() ?
- Collections.unmodifiableList(new ArrayList<Column>(cols)) :
- Collections.<Column>emptyList();
+ Collections.unmodifiableList(new ArrayList<ColumnImpl>(cols)) :
+ Collections.<ColumnImpl>emptyList();
}
/**
@@ -257,7 +257,7 @@ final class FKEnforcer
}
private boolean anyUpdates(Object[] oldRow, Object[] newRow) {
- for(Column col : _cols) {
+ for(ColumnImpl col : _cols) {
if(!MATCHER.matches(_table, col.getName(),
col.getRowValue(oldRow), col.getRowValue(newRow))) {
return true;
@@ -271,7 +271,7 @@ final class FKEnforcer
{
Table fromTable = joiner.getFromTable();
for(IndexData.ColumnDescriptor iCol : joiner.getColumns()) {
- Column col = iCol.getColumn();
+ ColumnImpl col = iCol.getColumn();
if(!MATCHER.matches(fromTable, col.getName(),
col.getRowValue(oldRow), col.getRowValue(newRow))) {
return true;
diff --git a/src/java/com/healthmarketscience/jackcess/GeneralLegacyIndexCodes.java b/src/java/com/healthmarketscience/jackcess/GeneralLegacyIndexCodes.java
index 6ddd62d..c5db96e 100644
--- a/src/java/com/healthmarketscience/jackcess/GeneralLegacyIndexCodes.java
+++ b/src/java/com/healthmarketscience/jackcess/GeneralLegacyIndexCodes.java
@@ -490,7 +490,7 @@ public class GeneralLegacyIndexCodes {
throws IOException
{
// first, convert to string
- String str = Column.toCharSequence(value).toString();
+ String str = ColumnImpl.toCharSequence(value).toString();
// all text columns (including memos) are only indexed up to the max
// number of chars in a VARCHAR column
diff --git a/src/java/com/healthmarketscience/jackcess/ImportFilter.java b/src/java/com/healthmarketscience/jackcess/ImportFilter.java
index 144b481..ca12b42 100644
--- a/src/java/com/healthmarketscience/jackcess/ImportFilter.java
+++ b/src/java/com/healthmarketscience/jackcess/ImportFilter.java
@@ -48,7 +48,7 @@ public interface ImportFilter {
* JDBC source
* @return the columns to use when creating the import table
*/
- public List<Column> filterColumns(List<Column> destColumns,
+ public List<ColumnImpl> filterColumns(List<ColumnImpl> destColumns,
ResultSetMetaData srcColumns)
throws SQLException, IOException;
diff --git a/src/java/com/healthmarketscience/jackcess/ImportUtil.java b/src/java/com/healthmarketscience/jackcess/ImportUtil.java
index b3ccb94..da2b502 100644
--- a/src/java/com/healthmarketscience/jackcess/ImportUtil.java
+++ b/src/java/com/healthmarketscience/jackcess/ImportUtil.java
@@ -68,12 +68,12 @@ public class ImportUtil
*
* @return a List of Columns
*/
- public static List<Column> toColumns(ResultSetMetaData md)
+ public static List<ColumnImpl> toColumns(ResultSetMetaData md)
throws SQLException
{
- List<Column> columns = new LinkedList<Column>();
+ List<ColumnImpl> columns = new LinkedList<ColumnImpl>();
for (int i = 1; i <= md.getColumnCount(); i++) {
- Column column = new Column();
+ ColumnImpl column = new ColumnImpl();
column.setName(DatabaseImpl.escapeIdentifier(md.getColumnName(i)));
int lengthInUnits = md.getColumnDisplaySize(i);
column.setSQLType(md.getColumnType(i), lengthInUnits);
@@ -167,7 +167,7 @@ public class ImportUtil
name = DatabaseImpl.escapeIdentifier(name);
Table table = null;
if(!useExistingTable || ((table = db.getTable(name)) == null)) {
- List<Column> columns = toColumns(md);
+ List<ColumnImpl> columns = toColumns(md);
table = createUniqueTable(db, name, columns, md, filter);
}
@@ -457,7 +457,7 @@ public class ImportUtil
Table table = null;
if(!useExistingTable || ((table = db.getTable(name)) == null)) {
- List<Column> columns = new LinkedList<Column>();
+ List<ColumnImpl> columns = new LinkedList<ColumnImpl>();
Object[] columnNames = splitLine(line, delimPat, quote, in, 0);
for (int i = 0; i < columnNames.length; i++) {
@@ -591,7 +591,7 @@ public class ImportUtil
* Returns a new table with a unique name and the given table definition.
*/
private static Table createUniqueTable(DatabaseImpl db, String name,
- List<Column> columns,
+ List<ColumnImpl> columns,
ResultSetMetaData md,
ImportFilter filter)
throws IOException, SQLException
diff --git a/src/java/com/healthmarketscience/jackcess/Index.java b/src/java/com/healthmarketscience/jackcess/Index.java
index d27e7e1..cffd63b 100644
--- a/src/java/com/healthmarketscience/jackcess/Index.java
+++ b/src/java/com/healthmarketscience/jackcess/Index.java
@@ -40,7 +40,7 @@ public abstract class Index
/**
* @return the Columns for this index (unmodifiable)
*/
- public abstract List<? extends ColumnInfo> getColumns();
+ public abstract List<? extends Index.Column> getColumns();
/**
* @return the Index referenced by this Index's ForeignKeyReference (if it
@@ -49,6 +49,11 @@ public abstract class Index
public abstract Index getReferencedIndex() throws IOException;
/**
+ * Whether or not {@code null} values are actually recorded in the index.
+ */
+ public abstract boolean shouldIgnoreNulls();
+
+ /**
* Whether or not index entries must be unique.
* <p>
* Some notes about uniqueness:
@@ -65,9 +70,9 @@ public abstract class Index
/**
* Information about a Column in an Index
*/
- public interface ColumnInfo {
+ public interface Column {
- public Column getColumn();
+ public com.healthmarketscience.jackcess.Column getColumn();
public boolean isAscending();
diff --git a/src/java/com/healthmarketscience/jackcess/IndexCursor.java b/src/java/com/healthmarketscience/jackcess/IndexCursor.java
index b0b41c0..acd129f 100644
--- a/src/java/com/healthmarketscience/jackcess/IndexCursor.java
+++ b/src/java/com/healthmarketscience/jackcess/IndexCursor.java
@@ -319,7 +319,7 @@ public class IndexCursor extends Cursor
}
@Override
- protected boolean findNextRowImpl(Column columnPattern, Object valuePattern)
+ protected boolean findNextRowImpl(ColumnImpl columnPattern, Object valuePattern)
throws IOException
{
if(!isBeforeFirst()) {
diff --git a/src/java/com/healthmarketscience/jackcess/IndexData.java b/src/java/com/healthmarketscience/jackcess/IndexData.java
index d6b411f..965e7d8 100644
--- a/src/java/com/healthmarketscience/jackcess/IndexData.java
+++ b/src/java/com/healthmarketscience/jackcess/IndexData.java
@@ -402,7 +402,7 @@ public class IndexData {
* @param tableBuffer table definition buffer to read from initial info
* @param availableColumns Columns that this index may use
*/
- public void read(ByteBuffer tableBuffer, List<Column> availableColumns)
+ public void read(ByteBuffer tableBuffer, List<ColumnImpl> availableColumns)
throws IOException
{
ByteUtil.forward(tableBuffer, getFormat().SKIP_BEFORE_INDEX); //Forward past Unknown
@@ -413,8 +413,8 @@ public class IndexData {
if (columnNumber != COLUMN_UNUSED) {
// find the desired column by column number (which is not necessarily
// the same as the column index)
- Column idxCol = null;
- for(Column col : availableColumns) {
+ ColumnImpl idxCol = null;
+ for(ColumnImpl col : availableColumns) {
if(col.getColumnNumber() == columnNumber) {
idxCol = col;
break;
@@ -483,7 +483,7 @@ public class IndexData {
flags = idxCol.getFlags();
// find actual table column number
- for(Column col : creator.getColumns()) {
+ for(ColumnImpl col : creator.getColumns()) {
if(col.getName().equalsIgnoreCase(idxCol.getName())) {
columnNumber = col.getColumnNumber();
break;
@@ -1099,7 +1099,7 @@ public class IndexData {
for(ColumnDescriptor col : _columns) {
Object value = values[col.getColumnIndex()];
- if(Column.isRawData(value)) {
+ if(ColumnImpl.isRawData(value)) {
// ignore it, we could not parse it
continue;
}
@@ -1169,7 +1169,7 @@ public class IndexData {
/**
* Writes the value of the given column type to a byte array and returns it.
*/
- private static byte[] encodeNumberColumnValue(Object value, Column column)
+ private static byte[] encodeNumberColumnValue(Object value, ColumnImpl column)
throws IOException
{
// always write in big endian order
@@ -1186,17 +1186,17 @@ public class IndexData {
/**
* Constructs a ColumnDescriptor of the relevant type for the given Column.
*/
- private ColumnDescriptor newColumnDescriptor(Column col, byte flags)
+ private ColumnDescriptor newColumnDescriptor(ColumnImpl col, byte flags)
throws IOException
{
switch(col.getType()) {
case TEXT:
case MEMO:
- Column.SortOrder sortOrder = col.getTextSortOrder();
- if(Column.GENERAL_LEGACY_SORT_ORDER.equals(sortOrder)) {
+ ColumnImpl.SortOrder sortOrder = col.getTextSortOrder();
+ if(ColumnImpl.GENERAL_LEGACY_SORT_ORDER.equals(sortOrder)) {
return new GenLegTextColumnDescriptor(col, flags);
}
- if(Column.GENERAL_SORT_ORDER.equals(sortOrder)) {
+ if(ColumnImpl.GENERAL_SORT_ORDER.equals(sortOrder)) {
return new GenTextColumnDescriptor(col, flags);
}
// unsupported sort order
@@ -1270,19 +1270,19 @@ public class IndexData {
* Information about the columns in an index. Also encodes new index
* values.
*/
- public static abstract class ColumnDescriptor implements Index.ColumnInfo
+ public static abstract class ColumnDescriptor implements Index.Column
{
- private final Column _column;
+ private final ColumnImpl _column;
private final byte _flags;
- private ColumnDescriptor(Column column, byte flags)
+ private ColumnDescriptor(ColumnImpl column, byte flags)
throws IOException
{
_column = column;
_flags = flags;
}
- public Column getColumn() {
+ public ColumnImpl getColumn() {
return _column;
}
@@ -1336,7 +1336,7 @@ public class IndexData {
*/
private static final class IntegerColumnDescriptor extends ColumnDescriptor
{
- private IntegerColumnDescriptor(Column column, byte flags)
+ private IntegerColumnDescriptor(ColumnImpl column, byte flags)
throws IOException
{
super(column, flags);
@@ -1368,7 +1368,7 @@ public class IndexData {
private static final class FloatingPointColumnDescriptor
extends ColumnDescriptor
{
- private FloatingPointColumnDescriptor(Column column, byte flags)
+ private FloatingPointColumnDescriptor(ColumnImpl column, byte flags)
throws IOException
{
super(column, flags);
@@ -1408,7 +1408,7 @@ public class IndexData {
private static class LegacyFixedPointColumnDescriptor
extends ColumnDescriptor
{
- private LegacyFixedPointColumnDescriptor(Column column, byte flags)
+ private LegacyFixedPointColumnDescriptor(ColumnImpl column, byte flags)
throws IOException
{
super(column, flags);
@@ -1459,7 +1459,7 @@ public class IndexData {
private static final class FixedPointColumnDescriptor
extends LegacyFixedPointColumnDescriptor
{
- private FixedPointColumnDescriptor(Column column, byte flags)
+ private FixedPointColumnDescriptor(ColumnImpl column, byte flags)
throws IOException
{
super(column, flags);
@@ -1485,7 +1485,7 @@ public class IndexData {
*/
private static final class ByteColumnDescriptor extends ColumnDescriptor
{
- private ByteColumnDescriptor(Column column, byte flags)
+ private ByteColumnDescriptor(ColumnImpl column, byte flags)
throws IOException
{
super(column, flags);
@@ -1514,7 +1514,7 @@ public class IndexData {
*/
private static final class BooleanColumnDescriptor extends ColumnDescriptor
{
- private BooleanColumnDescriptor(Column column, byte flags)
+ private BooleanColumnDescriptor(ColumnImpl column, byte flags)
throws IOException
{
super(column, flags);
@@ -1531,7 +1531,7 @@ public class IndexData {
throws IOException
{
bout.write(
- Column.toBooleanValue(value) ?
+ ColumnImpl.toBooleanValue(value) ?
(isAscending() ? ASC_BOOLEAN_TRUE : DESC_BOOLEAN_TRUE) :
(isAscending() ? ASC_BOOLEAN_FALSE : DESC_BOOLEAN_FALSE));
}
@@ -1543,7 +1543,7 @@ public class IndexData {
private static final class GenLegTextColumnDescriptor
extends ColumnDescriptor
{
- private GenLegTextColumnDescriptor(Column column, byte flags)
+ private GenLegTextColumnDescriptor(ColumnImpl column, byte flags)
throws IOException
{
super(column, flags);
@@ -1564,7 +1564,7 @@ public class IndexData {
*/
private static final class GenTextColumnDescriptor extends ColumnDescriptor
{
- private GenTextColumnDescriptor(Column column, byte flags)
+ private GenTextColumnDescriptor(ColumnImpl column, byte flags)
throws IOException
{
super(column, flags);
@@ -1585,7 +1585,7 @@ public class IndexData {
*/
private static final class GuidColumnDescriptor extends ColumnDescriptor
{
- private GuidColumnDescriptor(Column column, byte flags)
+ private GuidColumnDescriptor(ColumnImpl column, byte flags)
throws IOException
{
super(column, flags);
@@ -1620,7 +1620,7 @@ public class IndexData {
*/
private static final class ReadOnlyColumnDescriptor extends ColumnDescriptor
{
- private ReadOnlyColumnDescriptor(Column column, byte flags)
+ private ReadOnlyColumnDescriptor(ColumnImpl column, byte flags)
throws IOException
{
super(column, flags);
diff --git a/src/java/com/healthmarketscience/jackcess/IndexImpl.java b/src/java/com/healthmarketscience/jackcess/IndexImpl.java
index c34bf89..0cfe19e 100644
--- a/src/java/com/healthmarketscience/jackcess/IndexImpl.java
+++ b/src/java/com/healthmarketscience/jackcess/IndexImpl.java
@@ -45,8 +45,7 @@ import org.apache.commons.logging.LogFactory;
*/
public class IndexImpl extends Index implements Comparable<IndexImpl>
{
-
- protected static final Log LOG = LogFactory.getLog(Index.class);
+ protected static final Log LOG = LogFactory.getLog(IndexImpl.class);
/** index type for primary key indexes */
static final byte PRIMARY_KEY_INDEX_TYPE = (byte)1;
diff --git a/src/java/com/healthmarketscience/jackcess/JetFormat.java b/src/java/com/healthmarketscience/jackcess/JetFormat.java
index 2a37120..e4efdb7 100644
--- a/src/java/com/healthmarketscience/jackcess/JetFormat.java
+++ b/src/java/com/healthmarketscience/jackcess/JetFormat.java
@@ -258,7 +258,7 @@ public abstract class JetFormat {
public final boolean LEGACY_NUMERIC_INDEXES;
public final Charset CHARSET;
- public final Column.SortOrder DEFAULT_SORT_ORDER;
+ public final ColumnImpl.SortOrder DEFAULT_SORT_ORDER;
/**
* @param channel the database file.
@@ -490,7 +490,7 @@ public abstract class JetFormat {
protected abstract int defineMaxIndexNameLength();
protected abstract Charset defineCharset();
- protected abstract Column.SortOrder defineDefaultSortOrder();
+ protected abstract ColumnImpl.SortOrder defineDefaultSortOrder();
protected abstract boolean defineLegacyNumericIndexes();
@@ -703,8 +703,8 @@ public abstract class JetFormat {
protected Charset defineCharset() { return Charset.defaultCharset(); }
@Override
- protected Column.SortOrder defineDefaultSortOrder() {
- return Column.GENERAL_LEGACY_SORT_ORDER;
+ protected ColumnImpl.SortOrder defineDefaultSortOrder() {
+ return ColumnImpl.GENERAL_LEGACY_SORT_ORDER;
}
@Override
@@ -921,8 +921,8 @@ public abstract class JetFormat {
protected Charset defineCharset() { return Charset.forName("UTF-16LE"); }
@Override
- protected Column.SortOrder defineDefaultSortOrder() {
- return Column.GENERAL_LEGACY_SORT_ORDER;
+ protected ColumnImpl.SortOrder defineDefaultSortOrder() {
+ return ColumnImpl.GENERAL_LEGACY_SORT_ORDER;
}
@Override
@@ -995,8 +995,8 @@ public abstract class JetFormat {
}
@Override
- protected Column.SortOrder defineDefaultSortOrder() {
- return Column.GENERAL_SORT_ORDER;
+ protected ColumnImpl.SortOrder defineDefaultSortOrder() {
+ return ColumnImpl.GENERAL_SORT_ORDER;
}
@Override
diff --git a/src/java/com/healthmarketscience/jackcess/NullMask.java b/src/java/com/healthmarketscience/jackcess/NullMask.java
index 5be5218..6b120e0 100644
--- a/src/java/com/healthmarketscience/jackcess/NullMask.java
+++ b/src/java/com/healthmarketscience/jackcess/NullMask.java
@@ -73,7 +73,7 @@ public class NullMask {
* columns, returns the actual value of the column (where
* non-{@code null} == {@code true})
*/
- public boolean isNull(Column column) {
+ public boolean isNull(ColumnImpl column) {
int columnNumber = column.getColumnNumber();
// if new columns were added to the table, old null masks may not include
// them (meaning the field is null)
@@ -89,7 +89,7 @@ public class NullMask {
* boolean value is {@code true}).
* @param column column to be marked non-{@code null}
*/
- public void markNotNull(Column column) {
+ public void markNotNull(ColumnImpl column) {
int columnNumber = column.getColumnNumber();
int maskIndex = byteIndex(columnNumber);
_mask[maskIndex] = (byte) (_mask[maskIndex] | bitMask(columnNumber));
diff --git a/src/java/com/healthmarketscience/jackcess/PropertyMaps.java b/src/java/com/healthmarketscience/jackcess/PropertyMaps.java
index c40f1f3..2371841 100644
--- a/src/java/com/healthmarketscience/jackcess/PropertyMaps.java
+++ b/src/java/com/healthmarketscience/jackcess/PropertyMaps.java
@@ -253,7 +253,7 @@ public class PropertyMaps implements Iterable<PropertyMap>
private String readPropName(ByteBuffer buffer) {
int nameLength = buffer.getShort();
byte[] nameBytes = ByteUtil.getBytes(buffer, nameLength);
- return Column.decodeUncompressedText(nameBytes, _database.getCharset());
+ return ColumnImpl.decodeUncompressedText(nameBytes, _database.getCharset());
}
/**
@@ -302,7 +302,7 @@ public class PropertyMaps implements Iterable<PropertyMap>
/**
* Column adapted to work w/out a Table.
*/
- private class PropColumn extends Column
+ private class PropColumn extends ColumnImpl
{
@Override
public DatabaseImpl getDatabase() {
diff --git a/src/java/com/healthmarketscience/jackcess/Relationship.java b/src/java/com/healthmarketscience/jackcess/Relationship.java
index 43dd242..7c68546 100644
--- a/src/java/com/healthmarketscience/jackcess/Relationship.java
+++ b/src/java/com/healthmarketscience/jackcess/Relationship.java
@@ -59,10 +59,10 @@ public class Relationship {
private final Table _toTable;
/** the columns in the "from" table in this relationship (aligned w/
toColumns list) */
- private List<Column> _toColumns;
+ private List<ColumnImpl> _toColumns;
/** the columns in the "to" table in this relationship (aligned w/
toColumns list) */
- private List<Column> _fromColumns;
+ private List<ColumnImpl> _fromColumns;
/** the various flags describing this relationship */
private final int _flags;
@@ -71,11 +71,11 @@ public class Relationship {
{
_name = name;
_fromTable = fromTable;
- _fromColumns = new ArrayList<Column>(
- Collections.nCopies(numCols, (Column)null));
+ _fromColumns = new ArrayList<ColumnImpl>(
+ Collections.nCopies(numCols, (ColumnImpl)null));
_toTable = toTable;
- _toColumns = new ArrayList<Column>(
- Collections.nCopies(numCols, (Column)null));
+ _toColumns = new ArrayList<ColumnImpl>(
+ Collections.nCopies(numCols, (ColumnImpl)null));
_flags = flags;
}
@@ -87,7 +87,7 @@ public class Relationship {
return _fromTable;
}
- public List<Column> getFromColumns() {
+ public List<ColumnImpl> getFromColumns() {
return _fromColumns;
}
@@ -95,7 +95,7 @@ public class Relationship {
return _toTable;
}
- public List<Column> getToColumns() {
+ public List<ColumnImpl> getToColumns() {
return _toColumns;
}
diff --git a/src/java/com/healthmarketscience/jackcess/SimpleExportFilter.java b/src/java/com/healthmarketscience/jackcess/SimpleExportFilter.java
index 3669a94..17d0528 100644
--- a/src/java/com/healthmarketscience/jackcess/SimpleExportFilter.java
+++ b/src/java/com/healthmarketscience/jackcess/SimpleExportFilter.java
@@ -43,7 +43,7 @@ public class SimpleExportFilter implements ExportFilter {
public SimpleExportFilter() {
}
- public List<Column> filterColumns(List<Column> columns) throws IOException {
+ public List<ColumnImpl> filterColumns(List<ColumnImpl> columns) throws IOException {
return columns;
}
diff --git a/src/java/com/healthmarketscience/jackcess/SimpleImportFilter.java b/src/java/com/healthmarketscience/jackcess/SimpleImportFilter.java
index ba7eabb..38387ca 100644
--- a/src/java/com/healthmarketscience/jackcess/SimpleImportFilter.java
+++ b/src/java/com/healthmarketscience/jackcess/SimpleImportFilter.java
@@ -45,7 +45,7 @@ public class SimpleImportFilter implements ImportFilter {
public SimpleImportFilter() {
}
- public List<Column> filterColumns(List<Column> destColumns,
+ public List<ColumnImpl> filterColumns(List<ColumnImpl> destColumns,
ResultSetMetaData srcColumns)
throws SQLException, IOException
{
diff --git a/src/java/com/healthmarketscience/jackcess/Table.java b/src/java/com/healthmarketscience/jackcess/Table.java
index 1b7a359..b0dcf7e 100644
--- a/src/java/com/healthmarketscience/jackcess/Table.java
+++ b/src/java/com/healthmarketscience/jackcess/Table.java
@@ -84,7 +84,7 @@ public abstract class Table implements Iterable<Map<String, Object>>
* @return All of the columns in this table (unmodifiable List)
* @usage _general_method_
*/
- public abstract List<Column> getColumns();
+ public abstract List<? extends Column> getColumns();
/**
* @return the column with the given name
diff --git a/src/java/com/healthmarketscience/jackcess/TableBuilder.java b/src/java/com/healthmarketscience/jackcess/TableBuilder.java
index c1c8496..09b3876 100644
--- a/src/java/com/healthmarketscience/jackcess/TableBuilder.java
+++ b/src/java/com/healthmarketscience/jackcess/TableBuilder.java
@@ -41,7 +41,7 @@ public class TableBuilder {
/** name of the new table */
private String _name;
/** columns for the new table */
- private List<Column> _columns = new ArrayList<Column>();
+ private List<ColumnImpl> _columns = new ArrayList<ColumnImpl>();
/** indexes for the new table */
private List<IndexBuilder> _indexes = new ArrayList<IndexBuilder>();
/** whether or not table/column/index names are automatically escaped */
@@ -64,7 +64,7 @@ public class TableBuilder {
/**
* Adds a Column to the new table.
*/
- public TableBuilder addColumn(Column column) {
+ public TableBuilder addColumn(ColumnImpl column) {
if(_escapeIdentifiers) {
column.setName(DatabaseImpl.escapeIdentifier(column.getName()));
}
@@ -125,10 +125,10 @@ public class TableBuilder {
* Creates a new Table in the given Database with the currently configured
* attributes.
*/
- public Table toTable(DatabaseImpl db)
+ public Table toTable(Database db)
throws IOException
{
- db.createTable(_name, _columns, _indexes);
+ ((DatabaseImpl)db).createTable(_name, _columns, _indexes);
return db.getTable(_name);
}
diff --git a/src/java/com/healthmarketscience/jackcess/TableCreator.java b/src/java/com/healthmarketscience/jackcess/TableCreator.java
index bdf793e..bbe2df9 100644
--- a/src/java/com/healthmarketscience/jackcess/TableCreator.java
+++ b/src/java/com/healthmarketscience/jackcess/TableCreator.java
@@ -39,7 +39,7 @@ class TableCreator
{
private final DatabaseImpl _database;
private final String _name;
- private final List<Column> _columns;
+ private final List<ColumnImpl> _columns;
private final List<IndexBuilder> _indexes;
private final Map<IndexBuilder,IndexState> _indexStates =
new HashMap<IndexBuilder,IndexState>();
@@ -48,7 +48,7 @@ class TableCreator
private int _indexCount;
private int _logicalIndexCount;
- public TableCreator(DatabaseImpl database, String name, List<Column> columns,
+ public TableCreator(DatabaseImpl database, String name, List<ColumnImpl> columns,
List<IndexBuilder> indexes) {
_database = database;
_name = name;
@@ -77,7 +77,7 @@ class TableCreator
return _umapPageNumber;
}
- public List<Column> getColumns() {
+ public List<ColumnImpl> getColumns() {
return _columns;
}
@@ -153,7 +153,7 @@ class TableCreator
getFormat().MAX_COLUMNS_PER_TABLE + " columns");
}
- Column.SortOrder dbSortOrder = null;
+ ColumnImpl.SortOrder dbSortOrder = null;
try {
dbSortOrder = _database.getDefaultSortOrder();
} catch(IOException e) {
@@ -162,7 +162,7 @@ class TableCreator
Set<String> colNames = new HashSet<String>();
// next, validate the column definitions
- for(Column column : _columns) {
+ for(ColumnImpl column : _columns) {
// FIXME for now, we can't create complex columns
if(column.getType() == DataType.COMPLEX_TYPE) {
@@ -182,11 +182,11 @@ class TableCreator
}
}
- List<Column> autoCols = TableImpl.getAutoNumberColumns(_columns);
+ List<ColumnImpl> autoCols = TableImpl.getAutoNumberColumns(_columns);
if(autoCols.size() > 1) {
// for most autonumber types, we can only have one of each type
Set<DataType> autoTypes = EnumSet.noneOf(DataType.class);
- for(Column c : autoCols) {
+ for(ColumnImpl c : autoCols) {
if(!c.getType().isMultipleAutoNumberAllowed() &&
!autoTypes.add(c.getType())) {
throw new IllegalArgumentException(
diff --git a/src/java/com/healthmarketscience/jackcess/TableImpl.java b/src/java/com/healthmarketscience/jackcess/TableImpl.java
index 5459001..1e6c838 100644
--- a/src/java/com/healthmarketscience/jackcess/TableImpl.java
+++ b/src/java/com/healthmarketscience/jackcess/TableImpl.java
@@ -81,9 +81,9 @@ public class TableImpl extends Table
/** comparator which sorts variable length columns based on their index into
the variable length offset table */
- private static final Comparator<Column> VAR_LEN_COLUMN_COMPARATOR =
- new Comparator<Column>() {
- public int compare(Column c1, Column c2) {
+ private static final Comparator<ColumnImpl> VAR_LEN_COLUMN_COMPARATOR =
+ new Comparator<ColumnImpl>() {
+ public int compare(ColumnImpl c1, ColumnImpl c2) {
return ((c1.getVarLenTableIndex() < c2.getVarLenTableIndex()) ? -1 :
((c1.getVarLenTableIndex() > c2.getVarLenTableIndex()) ? 1 :
0));
@@ -91,9 +91,9 @@ public class TableImpl extends Table
};
/** comparator which sorts columns based on their display index */
- private static final Comparator<Column> DISPLAY_ORDER_COMPARATOR =
- new Comparator<Column>() {
- public int compare(Column c1, Column c2) {
+ private static final Comparator<ColumnImpl> DISPLAY_ORDER_COMPARATOR =
+ new Comparator<ColumnImpl>() {
+ public int compare(ColumnImpl c1, ColumnImpl c2) {
return ((c1.getDisplayIndex() < c2.getDisplayIndex()) ? -1 :
((c1.getDisplayIndex() > c2.getDisplayIndex()) ? 1 :
0));
@@ -123,11 +123,11 @@ public class TableImpl extends Table
/** max Number of variable columns in the table */
private short _maxVarColumnCount;
/** List of columns in this table, ordered by column number */
- private List<Column> _columns = new ArrayList<Column>();
+ private List<ColumnImpl> _columns = new ArrayList<ColumnImpl>();
/** List of variable length columns in this table, ordered by offset */
- private final List<Column> _varColumns = new ArrayList<Column>();
+ private final List<ColumnImpl> _varColumns = new ArrayList<ColumnImpl>();
/** List of autonumber columns in this table, ordered by column number */
- private List<Column> _autoNumColumns;
+ private List<ColumnImpl> _autoNumColumns;
/** List of indexes on this table (multiple logical indexes may be backed by
the same index data) */
private final List<IndexImpl> _indexes = new ArrayList<IndexImpl>();
@@ -135,7 +135,7 @@ public class TableImpl extends Table
index) */
private final List<IndexData> _indexDatas = new ArrayList<IndexData>();
/** List of columns in this table which are in one or more indexes */
- private final Set<Column> _indexColumns = new LinkedHashSet<Column>();
+ private final Set<ColumnImpl> _indexColumns = new LinkedHashSet<ColumnImpl>();
/** Table name as stored in Database */
private final String _name;
/** Usage map of pages that this table owns */
@@ -177,7 +177,7 @@ public class TableImpl extends Table
* Only used by unit tests
*/
- TableImpl(boolean testing, List<Column> columns) throws IOException {
+ TableImpl(boolean testing, List<ColumnImpl> columns) throws IOException {
if(!testing) {
throw new IllegalArgumentException();
}
@@ -302,13 +302,13 @@ public class TableImpl extends Table
}
@Override
- public List<Column> getColumns() {
+ public List<ColumnImpl> getColumns() {
return Collections.unmodifiableList(_columns);
}
@Override
- public Column getColumn(String name) {
- for(Column column : _columns) {
+ public ColumnImpl getColumn(String name) {
+ for(ColumnImpl column : _columns) {
if(column.getName().equalsIgnoreCase(name)) {
return column;
}
@@ -320,12 +320,12 @@ public class TableImpl extends Table
/**
* Only called by unit tests
*/
- private void setColumns(List<Column> columns) {
+ private void setColumns(List<ColumnImpl> columns) {
_columns = columns;
int colIdx = 0;
int varLenIdx = 0;
int fixedOffset = 0;
- for(Column col : _columns) {
+ for(ColumnImpl col : _columns) {
col.setColumnNumber((short)colIdx);
col.setColumnIndex(colIdx++);
if(col.isVariableLength()) {
@@ -470,7 +470,7 @@ public class TableImpl extends Table
// move to row data to get index values
rowBuffer = positionAtRowData(rowState, rowId);
- for(Column idxCol : _indexColumns) {
+ for(ColumnImpl idxCol : _indexColumns) {
getRowColumn(getFormat(), rowBuffer, idxCol, rowState, null);
}
@@ -528,7 +528,7 @@ public class TableImpl extends Table
* e.g. {@link Cursor#getCurrentRowValue}.
* @usage _advanced_method_
*/
- public Object getRowValue(RowState rowState, RowId rowId, Column column)
+ public Object getRowValue(RowState rowState, RowId rowId, ColumnImpl column)
throws IOException
{
if(this != column.getTable()) {
@@ -570,13 +570,13 @@ public class TableImpl extends Table
JetFormat format,
RowState rowState,
ByteBuffer rowBuffer,
- Collection<Column> columns,
+ Collection<ColumnImpl> columns,
Collection<String> columnNames)
throws IOException
{
Map<String, Object> rtn = new LinkedHashMap<String, Object>(
columns.size());
- for(Column column : columns) {
+ for(ColumnImpl column : columns) {
if((columnNames == null) || (columnNames.contains(column.getName()))) {
// Add the value to the row data
@@ -593,9 +593,9 @@ public class TableImpl extends Table
*/
private static Object getRowColumn(JetFormat format,
ByteBuffer rowBuffer,
- Column column,
+ ColumnImpl column,
RowState rowState,
- Map<Column,byte[]> rawVarValues)
+ Map<ColumnImpl,byte[]> rawVarValues)
throws IOException
{
byte[] columnData = null;
@@ -675,7 +675,7 @@ public class TableImpl extends Table
// cache "raw" row value. see note about caching above
rowState.setRowValue(column.getColumnIndex(),
- Column.rawDataWrapper(columnData));
+ ColumnImpl.rawDataWrapper(columnData));
return rowState.handleRowError(column, columnData, e);
}
@@ -910,7 +910,7 @@ public class TableImpl extends Table
// total up the amount of space used by the column and index names (2
// bytes per char + 2 bytes for the length)
- for(Column col : creator.getColumns()) {
+ for(ColumnImpl col : creator.getColumns()) {
int nameByteLen = (col.getName().length() *
JetFormat.TEXT_FIELD_UNIT_SIZE);
totalTableDefSize += nameByteLen + 2;
@@ -935,7 +935,7 @@ public class TableImpl extends Table
}
// column definitions
- Column.writeDefinitions(creator, buffer);
+ ColumnImpl.writeDefinitions(creator, buffer);
if(creator.hasIndexes()) {
// index and index data definitions
@@ -1011,7 +1011,7 @@ public class TableImpl extends Table
TableCreator creator, ByteBuffer buffer, int totalTableDefSize)
throws IOException
{
- List<Column> columns = creator.getColumns();
+ List<ColumnImpl> columns = creator.getColumns();
//Start writing the tdef
writeTablePageHeader(buffer);
@@ -1025,7 +1025,7 @@ public class TableImpl extends Table
}
buffer.put(TYPE_USER); //Table type
buffer.putShort((short) columns.size()); //Max columns a row will have
- buffer.putShort(Column.countVariableLength(columns)); //Number of variable columns in table
+ buffer.putShort(ColumnImpl.countVariableLength(columns)); //Number of variable columns in table
buffer.putShort((short) columns.size()); //Number of columns in table
buffer.putInt(creator.getLogicalIndexCount()); //Number of logical indexes in table
buffer.putInt(creator.getIndexCount()); //Number of indexes in table
@@ -1061,7 +1061,7 @@ public class TableImpl extends Table
*/
static void writeName(ByteBuffer buffer, String name, Charset charset)
{
- ByteBuffer encName = Column.encodeUncompressedText(name, charset);
+ ByteBuffer encName = ColumnImpl.encodeUncompressedText(name, charset);
buffer.putShort((short) encName.remaining());
buffer.put(encName);
}
@@ -1206,7 +1206,7 @@ public class TableImpl extends Table
_indexCount * getFormat().SIZE_INDEX_DEFINITION;
int dispIndex = 0;
for (int i = 0; i < columnCount; i++) {
- Column column = new Column(this, tableBuffer,
+ ColumnImpl column = new ColumnImpl(this, tableBuffer,
colOffset + (i * getFormat().SIZE_COLUMN_HEADER), dispIndex++);
_columns.add(column);
if(column.isVariableLength()) {
@@ -1218,7 +1218,7 @@ public class TableImpl extends Table
tableBuffer.position(colOffset +
(columnCount * getFormat().SIZE_COLUMN_HEADER));
for (int i = 0; i < columnCount; i++) {
- Column column = _columns.get(i);
+ ColumnImpl column = _columns.get(i);
column.setName(readName(tableBuffer));
}
Collections.sort(_columns);
@@ -1226,7 +1226,7 @@ public class TableImpl extends Table
// setup the data index for the columns
int colIdx = 0;
- for(Column col : _columns) {
+ for(ColumnImpl col : _columns) {
col.setColumnIndex(colIdx++);
}
@@ -1261,7 +1261,7 @@ public class TableImpl extends Table
Collections.sort(_columns, DISPLAY_ORDER_COMPARATOR);
}
- for(Column col : _columns) {
+ for(ColumnImpl col : _columns) {
// some columns need to do extra work after the table is completely
// loaded
col.postTableLoadInit();
@@ -1295,7 +1295,7 @@ public class TableImpl extends Table
private String readName(ByteBuffer buffer) {
int nameLength = readNameLength(buffer);
byte[] nameBytes = ByteUtil.getBytes(buffer, nameLength);
- return Column.decodeUncompressedText(nameBytes,
+ return ColumnImpl.decodeUncompressedText(nameBytes,
getDatabase().getCharset());
}
@@ -1328,7 +1328,7 @@ public class TableImpl extends Table
if(rowMap == null) {
return row;
}
- for(Column col : _columns) {
+ for(ColumnImpl col : _columns) {
if(rowMap.containsKey(col.getName())) {
col.setRowValue(row, col.getRowValue(rowMap));
}
@@ -1464,10 +1464,10 @@ public class TableImpl extends Table
// hang on to the raw values of var length columns we are "keeping". this
// will allow us to re-use pre-written var length data, which can save
// space for things like long value columns.
- Map<Column,byte[]> keepRawVarValues =
- (!_varColumns.isEmpty() ? new HashMap<Column,byte[]>() : null);
+ Map<ColumnImpl,byte[]> keepRawVarValues =
+ (!_varColumns.isEmpty() ? new HashMap<ColumnImpl,byte[]>() : null);
- for(Column column : _columns) {
+ for(ColumnImpl column : _columns) {
if(_autoNumColumns.contains(column)) {
// fill in any auto-numbers (we don't allow autonumber values to be
// modified)
@@ -1675,7 +1675,7 @@ public class TableImpl extends Table
ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer)
throws IOException
{
- return createRow(rowArray, buffer, 0, Collections.<Column,byte[]>emptyMap());
+ return createRow(rowArray, buffer, 0, Collections.<ColumnImpl,byte[]>emptyMap());
}
/**
@@ -1689,7 +1689,7 @@ public class TableImpl extends Table
* @return the given buffer, filled with the row data
*/
private ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer,
- int minRowSize, Map<Column,byte[]> rawVarValues)
+ int minRowSize, Map<ColumnImpl,byte[]> rawVarValues)
throws IOException
{
buffer.putShort(_maxColumnCount);
@@ -1698,7 +1698,7 @@ public class TableImpl extends Table
//Fixed length column data comes first
int fixedDataStart = buffer.position();
int fixedDataEnd = fixedDataStart;
- for (Column col : _columns) {
+ for (ColumnImpl col : _columns) {
if(col.isVariableLength()) {
continue;
@@ -1708,7 +1708,7 @@ public class TableImpl extends Table
if (col.getType() == DataType.BOOLEAN) {
- if(Column.toBooleanValue(rowValue)) {
+ if(ColumnImpl.toBooleanValue(rowValue)) {
//Booleans are stored in the null mask
nullMask.markNotNull(col);
}
@@ -1756,7 +1756,7 @@ public class TableImpl extends Table
// for each non-null long value column we need to reserve a small
// amount of space so that we don't end up running out of row space
// later by being too greedy
- for (Column varCol : _varColumns) {
+ for (ColumnImpl varCol : _varColumns) {
if((varCol.getType().isLongValue()) &&
(varCol.getRowValue(rowArray) != null)) {
maxRowSize -= getFormat().SIZE_LONG_VALUE_DEF;
@@ -1766,7 +1766,7 @@ public class TableImpl extends Table
//Now write out variable length column data
short[] varColumnOffsets = new short[_maxVarColumnCount];
int varColumnOffsetsIndex = 0;
- for (Column varCol : _varColumns) {
+ for (ColumnImpl varCol : _varColumns) {
short offset = (short) buffer.position();
Object rowValue = varCol.getRowValue(rowArray);
if (rowValue != null) {
@@ -1844,9 +1844,9 @@ public class TableImpl extends Table
}
Object complexAutoNumber = null;
- for(Column col : _autoNumColumns) {
+ for(ColumnImpl col : _autoNumColumns) {
// ignore given row value, use next autonumber
- Column.AutoNumberGenerator autoNumGen = col.getAutoNumberGenerator();
+ ColumnImpl.AutoNumberGenerator autoNumGen = col.getAutoNumberGenerator();
Object rowValue = null;
if(autoNumGen.getType() != DataType.COMPLEX_TYPE) {
rowValue = autoNumGen.getNext(null);
@@ -1908,7 +1908,7 @@ public class TableImpl extends Table
rtn.append("\nIndex (data) count: " + _indexCount);
rtn.append("\nLogical Index count: " + _logicalIndexCount);
rtn.append("\nColumns:\n");
- for(Column col : _columns) {
+ for(ColumnImpl col : _columns) {
rtn.append(col);
}
rtn.append("\nIndexes:\n");
@@ -1937,8 +1937,8 @@ public class TableImpl extends Table
public String display(long limit) throws IOException {
reset();
StringBuilder rtn = new StringBuilder();
- for(Iterator<Column> iter = _columns.iterator(); iter.hasNext(); ) {
- Column col = iter.next();
+ for(Iterator<ColumnImpl> iter = _columns.iterator(); iter.hasNext(); ) {
+ ColumnImpl col = iter.next();
rtn.append(col.getName());
if (iter.hasNext()) {
rtn.append("\t");
@@ -2115,14 +2115,16 @@ public class TableImpl extends Table
* @return the "AutoNumber" columns in the given collection of columns.
* @usage _advanced_method_
*/
- public static List<Column> getAutoNumberColumns(Collection<Column> columns) {
- List<Column> autoCols = new ArrayList<Column>(1);
- for(Column c : columns) {
+ public static List<ColumnImpl> getAutoNumberColumns(
+ Collection<ColumnImpl> columns)
+ {
+ List<ColumnImpl> autoCols = new ArrayList<ColumnImpl>(1);
+ for(ColumnImpl c : columns) {
if(c.isAutoNumber()) {
autoCols.add(c);
}
}
- return (!autoCols.isEmpty() ? autoCols : Collections.<Column>emptyList());
+ return (!autoCols.isEmpty() ? autoCols : Collections.<ColumnImpl>emptyList());
}
/**
@@ -2392,8 +2394,7 @@ public class TableImpl extends Table
return _finalRowBuffer;
}
- private Object handleRowError(Column column,
- byte[] columnData,
+ private Object handleRowError(ColumnImpl column, byte[] columnData,
Exception error)
throws IOException
{
diff --git a/src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java
index 7335729..7cdc45f 100644
--- a/src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java
+++ b/src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java
@@ -25,7 +25,7 @@ import java.util.List;
import java.util.Map;
import com.healthmarketscience.jackcess.ByteUtil;
-import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.ColumnImpl;
import com.healthmarketscience.jackcess.TableImpl;
@@ -39,27 +39,27 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
private static final String FILE_NAME_COL_NAME = "FileName";
private static final String FILE_TYPE_COL_NAME = "FileType";
- private final Column _fileUrlCol;
- private final Column _fileNameCol;
- private final Column _fileTypeCol;
- private final Column _fileDataCol;
- private final Column _fileTimeStampCol;
- private final Column _fileFlagsCol;
+ private final ColumnImpl _fileUrlCol;
+ private final ColumnImpl _fileNameCol;
+ private final ColumnImpl _fileTypeCol;
+ private final ColumnImpl _fileDataCol;
+ private final ColumnImpl _fileTimeStampCol;
+ private final ColumnImpl _fileFlagsCol;
- public AttachmentColumnInfo(Column column, int complexId,
+ public AttachmentColumnInfo(ColumnImpl column, int complexId,
TableImpl typeObjTable, TableImpl flatTable)
throws IOException
{
super(column, complexId, typeObjTable, flatTable);
- Column fileUrlCol = null;
- Column fileNameCol = null;
- Column fileTypeCol = null;
- Column fileDataCol = null;
- Column fileTimeStampCol = null;
- Column fileFlagsCol = null;
+ ColumnImpl fileUrlCol = null;
+ ColumnImpl fileNameCol = null;
+ ColumnImpl fileTypeCol = null;
+ ColumnImpl fileDataCol = null;
+ ColumnImpl fileTimeStampCol = null;
+ ColumnImpl fileFlagsCol = null;
- for(Column col : getTypeColumns()) {
+ for(ColumnImpl col : getTypeColumns()) {
switch(col.getType()) {
case TEXT:
if(FILE_NAME_COL_NAME.equalsIgnoreCase(col.getName())) {
@@ -100,27 +100,27 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
_fileFlagsCol = fileFlagsCol;
}
- public Column getFileUrlColumn() {
+ public ColumnImpl getFileUrlColumn() {
return _fileUrlCol;
}
- public Column getFileNameColumn() {
+ public ColumnImpl getFileNameColumn() {
return _fileNameCol;
}
- public Column getFileTypeColumn() {
+ public ColumnImpl getFileTypeColumn() {
return _fileTypeCol;
}
- public Column getFileDataColumn() {
+ public ColumnImpl getFileDataColumn() {
return _fileDataCol;
}
- public Column getFileTimeStampColumn() {
+ public ColumnImpl getFileTimeStampColumn() {
return _fileTimeStampCol;
}
- public Column getFileFlagsColumn() {
+ public ColumnImpl getFileFlagsColumn() {
return _fileFlagsCol;
}
@@ -187,7 +187,7 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
// attachment data has these columns FileURL(MEMO), FileName(TEXT),
// FileType(TEXT), FileData(OLE), FileTimeStamp(SHORT_DATE_TIME),
// FileFlags(LONG)
- List<Column> typeCols = typeObjTable.getColumns();
+ List<ColumnImpl> typeCols = typeObjTable.getColumns();
if(typeCols.size() < 6) {
return false;
}
@@ -198,7 +198,7 @@ public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
int numOle= 0;
int numLong = 0;
- for(Column col : typeCols) {
+ for(ColumnImpl col : typeCols) {
switch(col.getType()) {
case TEXT:
++numText;
diff --git a/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java
index d78c4a2..73a8894 100644
--- a/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java
+++ b/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java
@@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map;
import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.ColumnImpl;
import com.healthmarketscience.jackcess.CursorBuilder;
import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.DatabaseImpl;
@@ -46,7 +47,7 @@ import org.apache.commons.logging.LogFactory;
*/
public abstract class ComplexColumnInfo<V extends ComplexValue>
{
- private static final Log LOG = LogFactory.getLog(Column.class);
+ private static final Log LOG = LogFactory.getLog(ComplexColumnInfo.class);
public static final int INVALID_ID = -1;
public static final ComplexValueForeignKey INVALID_COMPLEX_VALUE_ID =
@@ -56,16 +57,16 @@ public abstract class ComplexColumnInfo<V extends ComplexValue>
private static final String COL_TABLE_ID = "ConceptualTableID";
private static final String COL_FLAT_TABLE_ID = "FlatTableID";
- private final Column _column;
+ private final ColumnImpl _column;
private final int _complexTypeId;
private final TableImpl _flatTable;
- private final List<Column> _typeCols;
- private final Column _pkCol;
- private final Column _complexValFkCol;
+ private final List<ColumnImpl> _typeCols;
+ private final ColumnImpl _pkCol;
+ private final ColumnImpl _complexValFkCol;
private IndexCursor _pkCursor;
private IndexCursor _complexValIdCursor;
- protected ComplexColumnInfo(Column column, int complexTypeId,
+ protected ComplexColumnInfo(ColumnImpl column, int complexTypeId,
TableImpl typeObjTable, TableImpl flatTable)
throws IOException
{
@@ -76,15 +77,15 @@ public abstract class ComplexColumnInfo<V extends ComplexValue>
// the flat table has all the "value" columns and 2 extra columns, a
// primary key for each row, and a LONG value which is essentially a
// foreign key to the main table.
- List<Column> typeCols = new ArrayList<Column>();
- List<Column> otherCols = new ArrayList<Column>();
+ List<ColumnImpl> typeCols = new ArrayList<ColumnImpl>();
+ List<ColumnImpl> otherCols = new ArrayList<ColumnImpl>();
diffFlatColumns(typeObjTable, flatTable, typeCols, otherCols);
_typeCols = Collections.unmodifiableList(typeCols);
- Column pkCol = null;
- Column complexValFkCol = null;
- for(Column col : otherCols) {
+ ColumnImpl pkCol = null;
+ ColumnImpl complexValFkCol = null;
+ for(ColumnImpl col : otherCols) {
if(col.isAutoNumber()) {
pkCol = col;
} else if(col.getType() == DataType.LONG) {
@@ -102,7 +103,7 @@ public abstract class ComplexColumnInfo<V extends ComplexValue>
}
public static ComplexColumnInfo<? extends ComplexValue> create(
- Column column, ByteBuffer buffer, int offset)
+ ColumnImpl column, ByteBuffer buffer, int offset)
throws IOException
{
int complexTypeId = buffer.getInt(
@@ -158,7 +159,7 @@ public abstract class ComplexColumnInfo<V extends ComplexValue>
// nothing to do in base class
}
- public Column getColumn() {
+ public ColumnImpl getColumn() {
return _column;
}
@@ -174,15 +175,15 @@ public abstract class ComplexColumnInfo<V extends ComplexValue>
return getDatabase().getPageChannel();
}
- public Column getPrimaryKeyColumn() {
+ public ColumnImpl getPrimaryKeyColumn() {
return _pkCol;
}
- public Column getComplexValueForeignKeyColumn() {
+ public ColumnImpl getComplexValueForeignKeyColumn() {
return _complexValFkCol;
}
- protected List<Column> getTypeColumns() {
+ protected List<ColumnImpl> getTypeColumns() {
return _typeCols;
}
@@ -372,12 +373,12 @@ public abstract class ComplexColumnInfo<V extends ComplexValue>
protected static void diffFlatColumns(TableImpl typeObjTable,
TableImpl flatTable,
- List<Column> typeCols,
- List<Column> otherCols)
+ List<ColumnImpl> typeCols,
+ List<ColumnImpl> otherCols)
{
// each "flat"" table has the columns from the "type" table, plus some
// others. separate the "flat" columns into these 2 buckets
- for(Column col : flatTable.getColumns()) {
+ for(ColumnImpl col : flatTable.getColumns()) {
boolean found = false;
try {
typeObjTable.getColumn(col.getName());
@@ -433,7 +434,7 @@ public abstract class ComplexColumnInfo<V extends ComplexValue>
_complexValueFk = complexValueFk;
}
- public Column getColumn() {
+ public ColumnImpl getColumn() {
return _complexValueFk.getColumn();
}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java b/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java
index 80735e3..b1b83a3 100644
--- a/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java
+++ b/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java
@@ -25,7 +25,7 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
-import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.ColumnImpl;
/**
* Value which is returned for a complex column. This value corresponds to a
@@ -45,11 +45,11 @@ public class ComplexValueForeignKey extends Number
{
private static final long serialVersionUID = 20110805L;
- private transient final Column _column;
+ private transient final ColumnImpl _column;
private final int _value;
private transient List<? extends ComplexValue> _values;
- public ComplexValueForeignKey(Column column, int value) {
+ public ComplexValueForeignKey(ColumnImpl column, int value) {
_column = column;
_value = value;
}
@@ -58,7 +58,7 @@ public class ComplexValueForeignKey extends Number
return _value;
}
- public Column getColumn() {
+ public ColumnImpl getColumn() {
return _column;
}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/MultiValueColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/MultiValueColumnInfo.java
index 2cf504c..f0f449a 100644
--- a/src/java/com/healthmarketscience/jackcess/complex/MultiValueColumnInfo.java
+++ b/src/java/com/healthmarketscience/jackcess/complex/MultiValueColumnInfo.java
@@ -25,7 +25,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.ColumnImpl;
import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.TableImpl;
@@ -40,9 +40,9 @@ public class MultiValueColumnInfo extends ComplexColumnInfo<SingleValue>
DataType.BYTE, DataType.INT, DataType.LONG, DataType.FLOAT,
DataType.DOUBLE, DataType.GUID, DataType.NUMERIC, DataType.TEXT);
- private final Column _valueCol;
+ private final ColumnImpl _valueCol;
- public MultiValueColumnInfo(Column column, int complexId,
+ public MultiValueColumnInfo(ColumnImpl column, int complexId,
TableImpl typeObjTable, TableImpl flatTable)
throws IOException
{
@@ -57,7 +57,7 @@ public class MultiValueColumnInfo extends ComplexColumnInfo<SingleValue>
return ComplexDataType.MULTI_VALUE;
}
- public Column getValueColumn() {
+ public ColumnImpl getValueColumn() {
return _valueCol;
}
@@ -91,7 +91,7 @@ public class MultiValueColumnInfo extends ComplexColumnInfo<SingleValue>
public static boolean isMultiValueColumn(TableImpl typeObjTable) {
// if we found a single value of a "simple" type, then we are dealing with
// a multi-value column
- List<Column> typeCols = typeObjTable.getColumns();
+ List<ColumnImpl> typeCols = typeObjTable.getColumns();
return ((typeCols.size() == 1) &&
VALUE_TYPES.contains(typeCols.get(0).getType()));
}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java
index 25a28f7..6af1557 100644
--- a/src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java
+++ b/src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java
@@ -24,7 +24,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.ColumnImpl;
import com.healthmarketscience.jackcess.TableImpl;
/**
@@ -35,14 +35,14 @@ import com.healthmarketscience.jackcess.TableImpl;
public class UnsupportedColumnInfo extends ComplexColumnInfo<UnsupportedValue>
{
- public UnsupportedColumnInfo(Column column, int complexId, TableImpl typeObjTable,
+ public UnsupportedColumnInfo(ColumnImpl column, int complexId, TableImpl typeObjTable,
TableImpl flatTable)
throws IOException
{
super(column, complexId, typeObjTable, flatTable);
}
- public List<Column> getValueColumns() {
+ public List<ColumnImpl> getValueColumns() {
return getTypeColumns();
}
@@ -60,7 +60,7 @@ public class UnsupportedColumnInfo extends ComplexColumnInfo<UnsupportedValue>
int id = (Integer)getPrimaryKeyColumn().getRowValue(rawValue);
Map<String,Object> values = new LinkedHashMap<String,Object>();
- for(Column col : getValueColumns()) {
+ for(ColumnImpl col : getValueColumns()) {
col.setRowValue(values, col.getRowValue(rawValue));
}
@@ -72,7 +72,7 @@ public class UnsupportedColumnInfo extends ComplexColumnInfo<UnsupportedValue>
super.asRow(row, value);
Map<String,Object> values = value.getValues();
- for(Column col : getValueColumns()) {
+ for(ColumnImpl col : getValueColumns()) {
col.setRowValue(row, col.getRowValue(values));
}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/VersionHistoryColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/VersionHistoryColumnInfo.java
index dedcb53..1ef3d09 100644
--- a/src/java/com/healthmarketscience/jackcess/complex/VersionHistoryColumnInfo.java
+++ b/src/java/com/healthmarketscience/jackcess/complex/VersionHistoryColumnInfo.java
@@ -25,7 +25,7 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
-import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.ColumnImpl;
import com.healthmarketscience.jackcess.TableImpl;
/**
@@ -41,18 +41,18 @@ import com.healthmarketscience.jackcess.TableImpl;
*/
public class VersionHistoryColumnInfo extends ComplexColumnInfo<Version>
{
- private final Column _valueCol;
- private final Column _modifiedCol;
+ private final ColumnImpl _valueCol;
+ private final ColumnImpl _modifiedCol;
- public VersionHistoryColumnInfo(Column column, int complexId,
+ public VersionHistoryColumnInfo(ColumnImpl column, int complexId,
TableImpl typeObjTable, TableImpl flatTable)
throws IOException
{
super(column, complexId, typeObjTable, flatTable);
- Column valueCol = null;
- Column modifiedCol = null;
- for(Column col : getTypeColumns()) {
+ ColumnImpl valueCol = null;
+ ColumnImpl modifiedCol = null;
+ for(ColumnImpl col : getTypeColumns()) {
switch(col.getType()) {
case SHORT_DATE_TIME:
modifiedCol = col;
@@ -75,16 +75,16 @@ public class VersionHistoryColumnInfo extends ComplexColumnInfo<Version>
// link up with the actual versioned column. it should have the same name
// as the "value" column in the type table.
- Column versionedCol = getColumn().getTable().getColumn(
+ ColumnImpl versionedCol = getColumn().getTable().getColumn(
getValueColumn().getName());
versionedCol.setVersionHistoryColumn(getColumn());
}
- public Column getValueColumn() {
+ public ColumnImpl getValueColumn() {
return _valueCol;
}
- public Column getModifiedDateColumn() {
+ public ColumnImpl getModifiedDateColumn() {
return _modifiedCol;
}
@@ -154,7 +154,7 @@ public class VersionHistoryColumnInfo extends ComplexColumnInfo<Version>
public static boolean isVersionHistoryColumn(TableImpl typeObjTable) {
// version history data has these columns <value>(MEMO),
// <modified>(SHORT_DATE_TIME)
- List<Column> typeCols = typeObjTable.getColumns();
+ List<ColumnImpl> typeCols = typeObjTable.getColumns();
if(typeCols.size() < 2) {
return false;
}
@@ -162,7 +162,7 @@ public class VersionHistoryColumnInfo extends ComplexColumnInfo<Version>
int numMemo = 0;
int numDate = 0;
- for(Column col : typeCols) {
+ for(ColumnImpl col : typeCols) {
switch(col.getType()) {
case SHORT_DATE_TIME:
++numDate;