aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/InvalidValueException.java32
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java34
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java365
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java319
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/InternalColumnValidator.java62
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/LongValueColumnImpl.java64
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java65
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java487
-rw-r--r--src/test/java/com/healthmarketscience/jackcess/PropertiesTest.java167
9 files changed, 962 insertions, 633 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/InvalidValueException.java b/src/main/java/com/healthmarketscience/jackcess/InvalidValueException.java
new file mode 100644
index 0000000..adffc0f
--- /dev/null
+++ b/src/main/java/com/healthmarketscience/jackcess/InvalidValueException.java
@@ -0,0 +1,32 @@
+/*
+Copyright (c) 2018 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess;
+
+/**
+ * JackcessException which indicates that an invalid column value was provided
+ * in a database update.
+ *
+ * @author James Ahlborn
+ */
+public class InvalidValueException extends JackcessException
+{
+ private static final long serialVersionUID = 20180428L;
+
+ public InvalidValueException(String msg) {
+ super(msg);
+ }
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java b/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java
index e77963f..5b6ed2c 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java
@@ -21,6 +21,8 @@ import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import com.healthmarketscience.jackcess.InvalidValueException;
+
/**
* Utility code for dealing with calculated columns.
@@ -30,7 +32,7 @@ import java.nio.ByteOrder;
*
* @author James Ahlborn
*/
-class CalculatedColumnUtil
+class CalculatedColumnUtil
{
// offset to the int which specifies the length of the actual data
private static final int CALC_DATA_LEN_OFFSET = 16;
@@ -51,12 +53,12 @@ class CalculatedColumnUtil
/**
* Creates the appropriate ColumnImpl class for a calculated column and
* reads a column definition in from a buffer
- *
+ *
* @param args column construction info
* @usage _advanced_method_
*/
static ColumnImpl create(ColumnImpl.InitArgs args) throws IOException
- {
+ {
switch(args.type) {
case BOOLEAN:
return new CalcBooleanColImpl(args);
@@ -71,7 +73,7 @@ class CalculatedColumnUtil
if(args.type.getHasScalePrecision()) {
return new CalcNumericColImpl(args);
}
-
+
return new CalcColImpl(args);
}
@@ -82,7 +84,7 @@ class CalculatedColumnUtil
if(data.length < CALC_DATA_OFFSET) {
return data;
}
-
+
ByteBuffer buffer = PageChannel.wrap(data);
buffer.position(CALC_DATA_LEN_OFFSET);
int dataLen = buffer.getInt();
@@ -109,7 +111,7 @@ class CalculatedColumnUtil
*/
private static byte[] wrapCalculatedValue(byte[] data) {
int dataLen = data.length;
- data = ByteUtil.copyOf(data, 0, dataLen + CALC_EXTRA_DATA_LEN,
+ data = ByteUtil.copyOf(data, 0, dataLen + CALC_EXTRA_DATA_LEN,
CALC_DATA_OFFSET);
PageChannel.wrap(data).putInt(CALC_DATA_LEN_OFFSET, dataLen);
return data;
@@ -126,7 +128,7 @@ class CalculatedColumnUtil
buffer.position(CALC_DATA_OFFSET);
return buffer;
}
-
+
/**
* General calculated column implementation.
@@ -148,7 +150,7 @@ class CalculatedColumnUtil
}
@Override
- protected ByteBuffer writeRealData(Object obj, int remainingRowLength,
+ protected ByteBuffer writeRealData(Object obj, int remainingRowLength,
ByteOrder order)
throws IOException
{
@@ -185,7 +187,7 @@ class CalculatedColumnUtil
}
@Override
- protected ByteBuffer writeRealData(Object obj, int remainingRowLength,
+ protected ByteBuffer writeRealData(Object obj, int remainingRowLength,
ByteOrder order)
throws IOException
{
@@ -216,7 +218,7 @@ class CalculatedColumnUtil
}
@Override
- protected ByteBuffer writeRealData(Object obj, int remainingRowLength,
+ protected ByteBuffer writeRealData(Object obj, int remainingRowLength,
ByteOrder order)
throws IOException
{
@@ -249,12 +251,12 @@ class CalculatedColumnUtil
}
@Override
- protected ByteBuffer writeLongValue(byte[] value, int remainingRowLength)
+ protected ByteBuffer writeLongValue(byte[] value, int remainingRowLength)
throws IOException
{
return super.writeLongValue(
wrapCalculatedValue(value), remainingRowLength);
- }
+ }
}
/**
@@ -282,7 +284,7 @@ class CalculatedColumnUtil
}
@Override
- protected ByteBuffer writeRealData(Object obj, int remainingRowLength,
+ protected ByteBuffer writeRealData(Object obj, int remainingRowLength,
ByteOrder order)
throws IOException
{
@@ -337,14 +339,14 @@ class CalculatedColumnUtil
decVal = decVal.setScale(maxScale);
}
int scale = decVal.scale();
-
+
// check precision
if(decVal.precision() > getType().getMaxPrecision()) {
- throw new IOException(withErrorContext(
+ throw new InvalidValueException(withErrorContext(
"Numeric value is too big for specified precision "
+ getType().getMaxPrecision() + ": " + decVal));
}
-
+
// convert to unscaled BigInteger, big-endian bytes
byte[] intValBytes = toUnscaledByteArray(decVal, dataLen - 4);
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
index f8b01ee..c80b986 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
@@ -44,6 +44,7 @@ import java.util.regex.Pattern;
import com.healthmarketscience.jackcess.Column;
import com.healthmarketscience.jackcess.ColumnBuilder;
import com.healthmarketscience.jackcess.DataType;
+import com.healthmarketscience.jackcess.InvalidValueException;
import com.healthmarketscience.jackcess.PropertyMap;
import com.healthmarketscience.jackcess.Table;
import com.healthmarketscience.jackcess.complex.ComplexColumnInfo;
@@ -62,9 +63,9 @@ import org.apache.commons.logging.LogFactory;
* @usage _intermediate_class_
*/
public class ColumnImpl implements Column, Comparable<ColumnImpl> {
-
+
protected static final Log LOG = LogFactory.getLog(ColumnImpl.class);
-
+
/**
* Placeholder object for adding rows which indicates that the caller wants
* the RowId of the new row. Must be added as an extra value at the end of
@@ -88,31 +89,31 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
*/
static final long MILLIS_BETWEEN_EPOCH_AND_1900 =
25569L * MILLISECONDS_PER_DAY;
-
+
/**
* 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 "is updatable" field bit
* @usage _advanced_field_
@@ -141,7 +142,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
* the "general" text sort order, latest version (access 2010+)
* @usage _intermediate_field_
*/
- public static final SortOrder GENERAL_SORT_ORDER =
+ public static final SortOrder GENERAL_SORT_ORDER =
new SortOrder(GENERAL_SORT_ORDER_VALUE, (byte)1);
/** pattern matching textual guid strings (allows for optional surrounding
@@ -149,7 +150,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
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 =
+ private static final byte[] TEXT_COMPRESSION_HEADER =
{ (byte)0xFF, (byte)0XFE };
private static final char MIN_COMPRESS_CHAR = 1;
private static final char MAX_COMPRESS_CHAR = 0xFF;
@@ -185,10 +186,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
/** the auto number generator for this column (if autonumber column) */
private final AutoNumberGenerator _autoNumberGenerator;
/** properties for this column, if any */
- private PropertyMap _props;
+ private PropertyMap _props;
/** Validator for writing new values */
private ColumnValidator _validator = SimpleColumnValidator.INSTANCE;
-
+
/**
* @usage _advanced_method_
*/
@@ -213,7 +214,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
_fixedDataOffset = fixedOffset;
_varLenTableIndex = varLenIndex;
}
-
+
/**
* Read a column definition in from a buffer
* @usage _advanced_method_
@@ -225,19 +226,19 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
_name = args.name;
_displayIndex = args.displayIndex;
_type = args.type;
-
+
_columnNumber = args.buffer.getShort(
args.offset + getFormat().OFFSET_COLUMN_NUMBER);
_columnLength = args.buffer.getShort(
args.offset + getFormat().OFFSET_COLUMN_LENGTH);
-
+
_variableLength = ((args.flags & FIXED_LEN_FLAG_MASK) == 0);
- _autoNumber = ((args.flags &
+ _autoNumber = ((args.flags &
(AUTO_NUMBER_FLAG_MASK | AUTO_NUMBER_GUID_FLAG_MASK)) != 0);
_calculated = ((args.extFlags & CALCULATED_EXT_FLAG_MASK) != 0);
-
+
_autoNumberGenerator = createAutoNumberGenerator();
-
+
if(_variableLength) {
_varLenTableIndex = args.buffer.getShort(
args.offset + getFormat().OFFSET_COLUMN_VARIABLE_TABLE_INDEX);
@@ -248,7 +249,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
_varLenTableIndex = 0;
}
}
-
+
/**
* Creates the appropriate ColumnImpl class and reads a column definition in
* from a buffer
@@ -273,7 +274,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
colType = resultType;
}
}
-
+
try {
args.type = DataType.fromByte(colType);
} catch(IOException e) {
@@ -288,7 +289,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
if(calculated) {
return CalculatedColumnUtil.create(args);
}
-
+
switch(args.type) {
case TEXT:
return new TextColumnImpl(args);
@@ -306,7 +307,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
if(args.type.isLongValue()) {
return new LongValueColumnImpl(args);
}
-
+
return new ColumnImpl(args);
}
@@ -320,7 +321,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
void collectUsageMapPages(Collection<Integer> pages) {
// base does nothing
}
-
+
/**
* Secondary column initialization after the table is fully loaded.
*/
@@ -332,10 +333,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return _table;
}
- public DatabaseImpl getDatabase() {
+ public DatabaseImpl getDatabase() {
return getTable().getDatabase();
}
-
+
/**
* @usage _advanced_method_
*/
@@ -349,15 +350,15 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public PageChannel getPageChannel() {
return getDatabase().getPageChannel();
}
-
+
public String getName() {
return _name;
}
-
+
public boolean isVariableLength() {
return _variableLength;
}
-
+
public boolean isAutoNumber() {
return _autoNumber;
}
@@ -379,7 +380,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public void setColumnIndex(int newColumnIndex) {
_columnIndex = newColumnIndex;
}
-
+
/**
* @usage _advanced_method_
*/
@@ -390,11 +391,11 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public DataType getType() {
return _type;
}
-
+
public int getSQLType() throws SQLException {
return _type.getSQLType();
}
-
+
public boolean isCompressedUnicode() {
return false;
}
@@ -402,7 +403,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public byte getPrecision() {
return (byte)getType().getDefaultPrecision();
}
-
+
public byte getScale() {
return (byte)getType().getDefaultScale();
}
@@ -432,14 +433,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public boolean isCalculated() {
return _calculated;
}
-
+
/**
* @usage _advanced_method_
*/
public int getVarLenTableIndex() {
return _varLenTableIndex;
}
-
+
/**
* @usage _advanced_method_
*/
@@ -458,7 +459,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public boolean isAppendOnly() {
return (getVersionHistoryColumn() != null);
}
-
+
public ColumnImpl getVersionHistoryColumn() {
return null;
}
@@ -481,17 +482,55 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public boolean isHyperlink() {
return false;
}
-
+
public ComplexColumnInfo<? extends ComplexValue> getComplexInfo() {
return null;
}
+ void initColumnValidator() throws IOException {
+ // first initialize any "external" (user-defined) validator
+ setColumnValidator(null);
+
+ // next, initialize any "internal" (property defined) validators
+ initPropertiesValidator();
+ }
+
+ void initPropertiesValidator() throws IOException {
+
+ PropertyMap props = getProperties();
+
+ // if the "required" property is enabled, add appropriate validator
+ boolean required = (Boolean)props.getValue(PropertyMap.REQUIRED_PROP,
+ Boolean.FALSE);
+ if(required) {
+ _validator = new RequiredColValidator(_validator);
+ }
+
+ // if the "allow zero len" property is disabled (textual columns only),
+ // add appropriate validator
+ boolean allowZeroLen =
+ !getType().isTextual() ||
+ (Boolean)props.getValue(PropertyMap.ALLOW_ZERO_LEN_PROP,
+ Boolean.TRUE);
+ if(!allowZeroLen) {
+ _validator = new NoZeroLenColValidator(_validator);
+ }
+ }
+
+ void propertiesUpdated() throws IOException {
+ // discard any existing internal validators and re-compute them
+ _validator = getColumnValidator();
+ initPropertiesValidator();
+ }
+
public ColumnValidator getColumnValidator() {
- return _validator;
+ // unwrap any "internal" validator
+ return ((_validator instanceof InternalColumnValidator) ?
+ ((InternalColumnValidator)_validator).getExternal() : _validator);
}
-
+
public void setColumnValidator(ColumnValidator newValidator) {
-
+
if(isAutoNumber()) {
// cannot set autonumber validator (autonumber values are controlled
// internally)
@@ -502,7 +541,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
// just leave default validator instance alone
return;
}
-
+
if(newValidator == null) {
newValidator = getDatabase().getColumnValidatorFactory()
.createValidator(this);
@@ -510,13 +549,19 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
newValidator = SimpleColumnValidator.INSTANCE;
}
}
- _validator = newValidator;
+
+ // handle delegation if "internal" validator in use
+ if(_validator instanceof InternalColumnValidator) {
+ ((InternalColumnValidator)_validator).setExternal(newValidator);
+ } else {
+ _validator = newValidator;
+ }
}
-
+
byte getOriginalDataType() {
return _type.getValue();
}
-
+
private AutoNumberGenerator createAutoNumberGenerator() {
if(!_autoNumber || (_type == null)) {
return null;
@@ -550,21 +595,21 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}
return _props;
}
-
+
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);
}
@@ -572,7 +617,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public boolean storeInNullMask() {
return (getType() == DataType.BOOLEAN);
}
-
+
public boolean writeToNullMask(Object value) {
return toBooleanValue(value);
}
@@ -590,14 +635,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
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).order(order);
@@ -641,10 +686,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
/**
* 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
+ * @throws IOException if the value cannot be parsed
*/
private BigDecimal readCurrencyValue(ByteBuffer buffer)
throws IOException
@@ -652,7 +697,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
if(buffer.remaining() != 8) {
throw new IOException(withErrorContext("Invalid money value"));
}
-
+
return new BigDecimal(BigInteger.valueOf(buffer.getLong(0)), 4);
}
@@ -670,7 +715,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
// 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());
@@ -738,11 +783,11 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
// check precision
if(decVal.precision() > getPrecision()) {
- throw new IOException(withErrorContext(
+ throw new InvalidValueException(withErrorContext(
"Numeric value is too big for specified precision "
+ getPrecision() + ": " + decVal));
}
-
+
// convert to unscaled BigInteger, big-endian bytes
byte[] intValBytes = toUnscaledByteArray(
decVal, getType().getFixedSize() - 1);
@@ -770,11 +815,11 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
// with unsigned values, so we can drop the extra leading 0
intValBytes = ByteUtil.copyOf(intValBytes, 1, maxByteLen);
} else {
- throw new IOException(withErrorContext(
+ throw new InvalidValueException(withErrorContext(
"Too many bytes for valid BigInteger?"));
}
} else if(intValBytes.length < maxByteLen) {
- intValBytes = ByteUtil.copyOf(intValBytes, 0, maxByteLen,
+ intValBytes = ByteUtil.copyOf(intValBytes, 0, maxByteLen,
(maxByteLen - intValBytes.length));
}
return intValBytes;
@@ -791,7 +836,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
long time = fromDateDouble(Double.longBitsToDouble(dateBits));
return new DateExt(time, dateBits);
}
-
+
/**
* Returns a java long time value converted from an access date double.
* @usage _advanced_method_
@@ -829,7 +874,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
// _not_ the time distance from zero (as one would expect with "normal"
// numbers). therefore, we need to do a little number logic to convert
// the absolute time fraction into a normal distance from zero number.
- long timePart = Math.round((Math.abs(value) % 1.0) *
+ long timePart = Math.round((Math.abs(value) % 1.0) *
(double)MILLISECONDS_PER_DAY);
long time = datePart + timePart;
@@ -845,13 +890,13 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
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));
}
}
@@ -908,7 +953,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
/**
* @return an appropriate Date long value for the given object
*/
- private static long toDateLong(Object value)
+ private static long toDateLong(Object value)
{
return ((value instanceof Date) ?
((Date)value).getTime() :
@@ -925,8 +970,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
{
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).
@@ -939,8 +984,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
// 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.
*/
@@ -985,7 +1030,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
{
Matcher m = GUID_PATTERN.matcher(toCharSequence(value));
if(!m.matches()) {
- throw new IOException(withErrorContext("Invalid GUID: " + value));
+ throw new InvalidValueException(
+ withErrorContext("Invalid GUID: " + value));
}
ByteBuffer origBuffer = null;
@@ -1002,7 +1048,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
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
@@ -1027,7 +1073,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public Object validate(Object obj) throws IOException {
return _validator.validate(this, obj);
}
-
+
/**
* Serialize an Object into a raw byte value for this column in little
* endian order
@@ -1040,7 +1086,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
{
return write(obj, remainingRowLength, PageChannel.DEFAULT_BYTE_ORDER);
}
-
+
/**
* Serialize an Object into a raw byte value for this column
* @param obj Object to serialize
@@ -1059,14 +1105,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return writeRealData(obj, remainingRowLength, order);
}
- protected ByteBuffer writeRealData(Object obj, int remainingRowLength,
+ protected ByteBuffer writeRealData(Object obj, int remainingRowLength,
ByteOrder order)
throws IOException
{
if(!isVariableLength() || !getType().isVariableLength()) {
return writeFixedLengthField(obj, order);
}
-
+
// this is an "inline" var length field
switch(getType()) {
case NUMERIC:
@@ -1080,7 +1126,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
case TEXT:
return encodeTextValue(
obj, 0, getLengthInUnits(), false).order(order);
-
+
case BINARY:
case UNKNOWN_0D:
case UNSUPPORTED_VARLEN:
@@ -1172,7 +1218,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
case UNSUPPORTED_FIXEDLEN:
byte[] bytes = toByteArray(obj);
if(bytes.length != getLength()) {
- throw new IOException(withErrorContext(
+ throw new InvalidValueException(withErrorContext(
"Invalid fixed size binary data, size "
+ getLength() + ", got " + bytes.length));
}
@@ -1184,7 +1230,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}
return buffer;
}
-
+
/**
* Decodes a compressed or uncompressed text value.
*/
@@ -1198,7 +1244,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
(data[1] == TEXT_COMPRESSION_HEADER[1]));
if(isCompressed) {
-
+
// this is a whacky compression combo that switches back and forth
// between compressed/uncompressed using a 0x00 byte (starting in
// compressed mode)
@@ -1216,7 +1262,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
inCompressedMode = !inCompressedMode;
++dataEnd;
dataStart = dataEnd;
-
+
} else {
++dataEnd;
}
@@ -1225,9 +1271,9 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
decodeTextSegment(data, dataStart, dataEnd, inCompressedMode, textBuf);
return textBuf.toString();
-
+
}
-
+
return decodeUncompressedText(data, getCharset());
}
@@ -1236,7 +1282,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
* given status of the segment (compressed/uncompressed).
*/
private void decodeTextSegment(byte[] data, int dataStart, int dataEnd,
- boolean inCompressedMode,
+ boolean inCompressedMode,
StringBuilder textBuf)
{
if(dataEnd <= dataStart) {
@@ -1251,7 +1297,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
for(int i = dataStart; i < dataEnd; ++i) {
tmpData[tmpIdx] = data[i];
tmpIdx += 2;
- }
+ }
data = tmpData;
dataStart = 0;
dataLength = data.length;
@@ -1269,7 +1315,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
byte[] textBytes, int startPos, int length, Charset charset)
{
return charset.decode(ByteBuffer.wrap(textBytes, startPos, length));
- }
+ }
/**
* Encodes a text value, possibly compressing.
@@ -1280,23 +1326,23 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
{
CharSequence text = toCharSequence(obj);
if((text.length() > maxChars) || (text.length() < minChars)) {
- throw new IOException(withErrorContext(
+ throw new InvalidValueException(withErrorContext(
"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() &&
(text.length() <= getFormat().MAX_COMPRESSED_UNICODE_SIZE) &&
isUnicodeCompressible(text)) {
- byte[] encodedChars = new byte[TEXT_COMPRESSION_HEADER.length +
+ 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] =
+ encodedChars[i + TEXT_COMPRESSION_HEADER.length] =
(byte)text.charAt(i);
}
return ByteBuffer.wrap(encodedChars);
@@ -1353,7 +1399,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}
return flags;
}
-
+
@Override
public String toString() {
ToStringBuilder sb = CustomToStringStyle.builder(this)
@@ -1363,7 +1409,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
" (" + _type + ")")
.append("number", _columnNumber)
.append("length", _columnLength)
- .append("variableLength", _variableLength);
+ .append("variableLength", _variableLength);
if(_calculated) {
sb.append("calculated", _calculated);
}
@@ -1375,10 +1421,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}
if(isAppendOnly()) {
sb.append("appendOnly", isAppendOnly());
- }
+ }
if(isHyperlink()) {
sb.append("hyperlink", isHyperlink());
- }
+ }
}
if(_type.getHasScalePrecision()) {
sb.append("precision", getPrecision())
@@ -1392,14 +1438,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}
return sb.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,
+ public static String decodeUncompressedText(byte[] textBytes,
Charset charset)
{
return decodeUncompressedText(textBytes, 0, textBytes.length, charset)
@@ -1415,12 +1461,12 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public static ByteBuffer encodeUncompressedText(CharSequence text,
Charset charset)
{
- CharBuffer cb = ((text instanceof CharBuffer) ?
+ CharBuffer cb = ((text instanceof CharBuffer) ?
(CharBuffer)text : CharBuffer.wrap(text));
return charset.encode(cb);
}
-
+
/**
* Orders Columns by column number.
* @usage _general_method_
@@ -1434,7 +1480,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return 0;
}
}
-
+
/**
* @param columns A list of columns in a table definition
* @return The number of variable length columns found in the list
@@ -1513,7 +1559,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}
return Double.valueOf(value.toString());
}
-
+
/**
* @return an appropriate CharSequence representation of the given object.
* @usage _advanced_method_
@@ -1599,7 +1645,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
// Access considers 0 as "false"
if(obj instanceof BigDecimal) {
return (((BigDecimal)obj).compareTo(BigDecimal.ZERO) != 0);
- }
+ }
if(obj instanceof BigInteger) {
return (((BigInteger)obj).compareTo(BigInteger.ZERO) != 0);
}
@@ -1607,7 +1653,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}
return Boolean.parseBoolean(obj.toString());
}
-
+
/**
* Swaps the bytes of the given numeric in place.
*/
@@ -1670,7 +1716,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}
protected static void writeDefinition(
- TableMutator mutator, ColumnBuilder col, ByteBuffer buffer)
+ TableMutator mutator, ColumnBuilder col, ByteBuffer buffer)
throws IOException
{
TableMutator.ColumnOffsets colOffsets = mutator.getColumnOffsets();
@@ -1727,9 +1773,9 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
short length = col.getLength();
if(col.isCalculated()) {
// calced columns have additional value overhead
- if(!col.getType().isVariableLength() ||
+ if(!col.getType().isVariableLength() ||
col.getType().getHasScalePrecision()) {
- length = CalculatedColumnUtil.CALC_FIXED_FIELD_LEN;
+ length = CalculatedColumnUtil.CALC_FIXED_FIELD_LEN;
} else {
length += CalculatedColumnUtil.CALC_EXTRA_DATA_LEN;
}
@@ -1757,7 +1803,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
TableMutator.ColumnState colState = creator.getColumnState(lvalCol);
buffer.putShort(lvalCol.getColumnNumber());
-
+
// owned pages umap (both are on same page)
buffer.put(colState.getUmapOwnedRowNumber());
ByteUtil.put3ByteInt(buffer, colState.getUmapPageNumber());
@@ -1782,7 +1828,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
// 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;
@@ -1812,7 +1858,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
int extFlagsOffset = format.OFFSET_COLUMN_EXT_FLAGS;
return ((extFlagsOffset >= 0) ? buffer.get(offset + extFlagsOffset) : 0);
}
-
+
/**
* Writes the sort order info to the given buffer at the current position.
*/
@@ -1821,7 +1867,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
if(sortOrder == null) {
sortOrder = format.DEFAULT_SORT_ORDER;
}
- buffer.putShort(sortOrder.getValue());
+ buffer.putShort(sortOrder.getValue());
if(format.SIZE_SORT_ORDER == 4) {
buffer.put((byte)0x00); // unknown
buffer.put(sortOrder.getVersion());
@@ -1855,18 +1901,18 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
case BYTE:
return ((value instanceof Byte) ? value : toNumber(value, db).byteValue());
case INT:
- return ((value instanceof Short) ? value :
+ return ((value instanceof Short) ? value :
toNumber(value, db).shortValue());
case LONG:
- return ((value instanceof Integer) ? value :
+ return ((value instanceof Integer) ? value :
toNumber(value, db).intValue());
case MONEY:
return toBigDecimal(value, db);
case FLOAT:
- return ((value instanceof Float) ? value :
+ return ((value instanceof Float) ? value :
toNumber(value, db).floatValue());
case DOUBLE:
- return ((value instanceof Double) ? value :
+ return ((value instanceof Double) ? value :
toNumber(value, db).doubleValue());
case SHORT_DATE_TIME:
return ((value instanceof DateExt) ? value :
@@ -1874,7 +1920,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
case TEXT:
case MEMO:
case GUID:
- return ((value instanceof String) ? value :
+ return ((value instanceof String) ? value :
toCharSequence(value).toString());
case NUMERIC:
return toBigDecimal(value, db);
@@ -1882,7 +1928,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
// leave alone for now?
return value;
case BIG_INT:
- return ((value instanceof Long) ? value :
+ return ((value instanceof Long) ? value :
toNumber(value, db).longValue());
default:
// some variation of binary data
@@ -1896,7 +1942,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
private static String withErrorContext(
String msg, DatabaseImpl db, String tableName, String colName) {
- return msg + " (Db=" + db.getName() + ";Table=" + tableName + ";Column=" +
+ return msg + " (Db=" + db.getName() + ";Table=" + tableName + ";Column=" +
colName + ")";
}
@@ -1919,7 +1965,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
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)
@@ -1987,14 +2033,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
* "lost" for the table.</i>
*/
public abstract Object handleInsert(
- TableImpl.WriteRowState writeRowState, Object inRowValue)
+ TableImpl.WriteRowState writeRowState, Object inRowValue)
throws IOException;
/**
* Restores a previous autonumber generated by this generator.
*/
public abstract void restoreLast(Object last);
-
+
/**
* Returns the type of values generated by this generator.
*/
@@ -2019,12 +2065,13 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
@Override
public Object handleInsert(TableImpl.WriteRowState writeRowState,
- Object inRowValue)
+ Object inRowValue)
throws IOException
{
int inAutoNum = toNumber(inRowValue).intValue();
- if(inAutoNum <= INVALID_AUTO_NUMBER && !getTable().isAllowAutoNumberInsert()) {
- throw new IOException(withErrorContext(
+ if(inAutoNum <= INVALID_AUTO_NUMBER &&
+ !getTable().isAllowAutoNumberInsert()) {
+ throw new InvalidValueException(withErrorContext(
"Invalid auto number value " + inAutoNum));
}
// the table stores the last long autonumber used
@@ -2038,7 +2085,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
getTable().restoreLastLongAutoNumber((Integer)last);
}
}
-
+
@Override
public DataType getType() {
return DataType.LONG;
@@ -2065,7 +2112,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
@Override
public Object handleInsert(TableImpl.WriteRowState writeRowState,
- Object inRowValue)
+ Object inRowValue)
throws IOException
{
_lastAutoNumber = toCharSequence(inRowValue);
@@ -2076,7 +2123,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public void restoreLast(Object last) {
_lastAutoNumber = null;
}
-
+
@Override
public DataType getType() {
return DataType.GUID;
@@ -2102,13 +2149,13 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
nextComplexAutoNum = getTable().getNextComplexTypeAutoNumber();
writeRowState.setComplexAutoNumber(nextComplexAutoNum);
}
- return new ComplexValueForeignKeyImpl(ColumnImpl.this,
+ return new ComplexValueForeignKeyImpl(ColumnImpl.this,
nextComplexAutoNum);
}
@Override
public Object handleInsert(TableImpl.WriteRowState writeRowState,
- Object inRowValue)
+ Object inRowValue)
throws IOException
{
ComplexValueForeignKey inComplexFK = null;
@@ -2120,12 +2167,12 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}
if(inComplexFK.getColumn() != ColumnImpl.this) {
- throw new IOException(withErrorContext(
+ throw new InvalidValueException(withErrorContext(
"Wrong column for complex value foreign key, found " +
inComplexFK.getColumn().getName()));
}
if(inComplexFK.get() < 1) {
- throw new IOException(withErrorContext(
+ throw new InvalidValueException(withErrorContext(
"Invalid complex value foreign key value " + inComplexFK.get()));
}
// same value is shared across all ComplexType values in a row
@@ -2133,7 +2180,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
if(prevRowValue <= INVALID_AUTO_NUMBER) {
writeRowState.setComplexAutoNumber(inComplexFK.get());
} else if(prevRowValue != inComplexFK.get()) {
- throw new IOException(withErrorContext(
+ throw new InvalidValueException(withErrorContext(
"Inconsistent complex value foreign key values: found " +
prevRowValue + ", given " + inComplexFK));
}
@@ -2151,21 +2198,21 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
((ComplexValueForeignKey)last).get());
}
}
-
+
@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;
@@ -2186,14 +2233,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public void restoreLast(Object last) {
throw new UnsupportedOperationException();
}
-
+
@Override
public DataType getType() {
return _genType;
}
}
-
+
/**
* Information about the sort order (collation) for a textual column.
* @usage _intermediate_class_
@@ -2202,7 +2249,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
{
private final short _value;
private final byte _version;
-
+
public SortOrder(short value, byte version) {
_value = value;
_version = version;
@@ -2259,10 +2306,58 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
this.offset = offset;
this.name = name;
this.displayIndex = displayIndex;
-
+
this.colType = buffer.get(offset + table.getFormat().OFFSET_COLUMN_TYPE);
this.flags = buffer.get(offset + table.getFormat().OFFSET_COLUMN_FLAGS);
this.extFlags = readExtraFlags(buffer, offset, table.getFormat());
}
}
+
+ /**
+ * "Internal" column validator for columns with the "required" property
+ * enabled.
+ */
+ private static final class RequiredColValidator extends InternalColumnValidator
+ {
+ private RequiredColValidator(ColumnValidator delegate) {
+ super(delegate);
+ }
+
+ @Override
+ protected Object internalValidate(Column col, Object val)
+ throws IOException
+ {
+ if(val == null) {
+ throw new InvalidValueException(
+ ((ColumnImpl)col).withErrorContext(
+ "Missing value for required column"));
+ }
+ return val;
+ }
+ }
+
+ /**
+ * "Internal" column validator for text columns with the "allow zero len"
+ * property disabled.
+ */
+ private static final class NoZeroLenColValidator extends InternalColumnValidator
+ {
+ private NoZeroLenColValidator(ColumnValidator delegate) {
+ super(delegate);
+ }
+
+ @Override
+ protected Object internalValidate(Column col, Object val)
+ throws IOException
+ {
+ CharSequence valStr = ColumnImpl.toCharSequence(val);
+ // oddly enough null is allowed for non-zero len strings
+ if((valStr != null) && valStr.length() == 0) {
+ throw new InvalidValueException(
+ ((ColumnImpl)col).withErrorContext(
+ "Zero length string is not allowed"));
+ }
+ return valStr;
+ }
+ }
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java
index a3b3701..5bdc2ff 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java
@@ -83,7 +83,7 @@ import org.apache.commons.logging.LogFactory;
* @usage _intermediate_class_
*/
public class DatabaseImpl implements Database
-{
+{
private static final Log LOG = LogFactory.getLog(DatabaseImpl.class);
/** this is the default "userId" used if we cannot find existing info. this
@@ -94,11 +94,11 @@ public class DatabaseImpl implements Database
/** the default value for the resource path used to load classpath
* resources.
*/
- public static final String DEFAULT_RESOURCE_PATH =
+ public static final String DEFAULT_RESOURCE_PATH =
"com/healthmarketscience/jackcess/";
/** the resource path to be used when loading classpath resources */
- static final String RESOURCE_PATH =
+ static final String RESOURCE_PATH =
System.getProperty(RESOURCE_PATH_PROPERTY, DEFAULT_RESOURCE_PATH);
/** whether or not this jvm has "broken" nio support */
@@ -119,7 +119,7 @@ public class DatabaseImpl implements Database
addFileFormatDetails(FileFormat.V2016, "empty2016", JetFormat.VERSION_16);
addFileFormatDetails(FileFormat.MSISAM, null, JetFormat.VERSION_MSISAM);
}
-
+
/** System catalog always lives on page 2 */
private static final int PAGE_SYSTEM_CATALOG = 2;
/** Name of the system catalog */
@@ -155,7 +155,7 @@ public class DatabaseImpl implements Database
private static final String REL_COL_FROM_TABLE = "szReferencedObject";
/** Relationship table column name of the relationship */
private static final String REL_COL_NAME = "szRelationship";
-
+
/** System catalog column name of the page on which system object definitions
are stored */
private static final String CAT_COL_ID = "Id";
@@ -192,7 +192,7 @@ public class DatabaseImpl implements Database
/** this object is hidden */
public static final int HIDDEN_OBJECT_FLAG = 0x08;
/** all flags which seem to indicate some type of system object */
- static final int SYSTEM_OBJECT_FLAGS =
+ static final int SYSTEM_OBJECT_FLAGS =
SYSTEM_OBJECT_FLAG | ALT_SYSTEM_OBJECT_FLAG;
/** read-only channel access mode */
@@ -238,17 +238,17 @@ public class DatabaseImpl implements Database
CAT_COL_FLAGS, CAT_COL_PARENT_ID));
/** the columns to read when finding table details */
private static Collection<String> SYSTEM_CATALOG_TABLE_DETAIL_COLUMNS =
- new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID,
- CAT_COL_FLAGS, CAT_COL_PARENT_ID,
+ new HashSet<String>(Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID,
+ CAT_COL_FLAGS, CAT_COL_PARENT_ID,
CAT_COL_DATABASE, CAT_COL_FOREIGN_NAME));
/** the columns to read when getting object propertyes */
private static Collection<String> SYSTEM_CATALOG_PROPS_COLUMNS =
new HashSet<String>(Arrays.asList(CAT_COL_ID, CAT_COL_PROPS));
/** regex matching characters which are invalid in identifier names */
- private static final Pattern INVALID_IDENTIFIER_CHARS =
+ private static final Pattern INVALID_IDENTIFIER_CHARS =
Pattern.compile("[\\p{Cntrl}.!`\\]\\[]");
-
+
/** the File of the database */
private final File _file;
/** the simple name of the database */
@@ -357,14 +357,14 @@ public class DatabaseImpl implements Database
*/
public static DatabaseImpl open(
File mdbFile, boolean readOnly, FileChannel channel,
- boolean autoSync, Charset charset, TimeZone timeZone,
+ boolean autoSync, Charset charset, TimeZone timeZone,
CodecProvider provider)
throws IOException
{
boolean closeChannel = false;
if(channel == null) {
if(!mdbFile.exists() || !mdbFile.canRead()) {
- throw new FileNotFoundException("given file does not exist: " +
+ throw new FileNotFoundException("given file does not exist: " +
mdbFile);
}
@@ -404,7 +404,7 @@ public class DatabaseImpl implements Database
}
}
}
-
+
/**
* Create a new Database for the given fileFormat
* @param fileFormat version of new database.
@@ -425,18 +425,18 @@ public class DatabaseImpl implements Database
* @param timeZone TimeZone to use, if {@code null}, uses default
* @usage _advanced_method_
*/
- public static DatabaseImpl create(FileFormat fileFormat, File mdbFile,
+ public static DatabaseImpl create(FileFormat fileFormat, File mdbFile,
FileChannel channel, boolean autoSync,
Charset charset, TimeZone timeZone)
throws IOException
{
FileFormatDetails details = getFileFormatDetails(fileFormat);
if (details.getFormat().READ_ONLY) {
- throw new IOException("File format " + fileFormat +
+ throw new IOException("File format " + fileFormat +
" does not support writing for " + mdbFile);
}
if(details.getEmptyFilePath() == null) {
- throw new IOException("File format " + fileFormat +
+ throw new IOException("File format " + fileFormat +
" does not support file creation for " + mdbFile);
}
@@ -451,7 +451,7 @@ public class DatabaseImpl implements Database
channel.truncate(0);
transferDbFrom(channel, getResourceAsStream(details.getEmptyFilePath()));
channel.force(true);
- DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync,
+ DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync,
fileFormat, charset, timeZone, null);
success = true;
return db;
@@ -482,10 +482,10 @@ public class DatabaseImpl implements Database
final String mode = (readOnly ? RO_CHANNEL_MODE : RW_CHANNEL_MODE);
return new RandomAccessFile(mdbFile, mode).getChannel();
}
-
+
/**
* Create a new database by reading it in from a FileChannel.
- * @param file the File to which the channel is connected
+ * @param file the File to which the channel is connected
* @param channel File channel of the database. This needs to be a
* FileChannel instead of a ReadableByteChannel because we need to
* randomly jump around to various points in the file.
@@ -549,7 +549,7 @@ public class DatabaseImpl implements Database
public JetFormat getFormat() {
return _format;
}
-
+
/**
* @return The system catalog table
* @usage _advanced_method_
@@ -557,7 +557,7 @@ public class DatabaseImpl implements Database
public TableImpl getSystemCatalog() {
return _systemCatalog;
}
-
+
/**
* @return The system Access Control Entries table (loaded on demand)
* @usage _advanced_method_
@@ -586,7 +586,7 @@ public class DatabaseImpl implements Database
public void setErrorHandler(ErrorHandler newErrorHandler) {
_dbErrorHandler = newErrorHandler;
- }
+ }
public LinkResolver getLinkResolver() {
return((_linkResolver != null) ? _linkResolver : LinkResolver.DEFAULT);
@@ -594,10 +594,10 @@ public class DatabaseImpl implements Database
public void setLinkResolver(LinkResolver newLinkResolver) {
_linkResolver = newLinkResolver;
- }
+ }
public Map<String,Database> getLinkedDatabases() {
- return ((_linkedDbs == null) ? Collections.<String,Database>emptyMap() :
+ return ((_linkedDbs == null) ? Collections.<String,Database>emptyMap() :
Collections.unmodifiableMap(_linkedDbs));
}
@@ -619,7 +619,7 @@ public class DatabaseImpl implements Database
// but, the local table name may not match the remote table name, so we
// need to do a search if the common case fails
return _tableFinder.isLinkedTable(table);
- }
+ }
private boolean matchesLinkedTable(Table table, String linkedTableName,
String linkedDbName) {
@@ -627,7 +627,7 @@ public class DatabaseImpl implements Database
(_linkedDbs != null) &&
(_linkedDbs.get(linkedDbName) == table.getDatabase()));
}
-
+
public TimeZone getTimeZone() {
return _timeZone;
}
@@ -639,7 +639,7 @@ public class DatabaseImpl implements Database
_timeZone = newTimeZone;
// clear cached calendar when timezone is changed
_calendar = null;
- }
+ }
public Charset getCharset()
{
@@ -697,7 +697,7 @@ public class DatabaseImpl implements Database
}
_validatorFactory = newFactory;
}
-
+
/**
* @usage _advanced_method_
*/
@@ -761,9 +761,9 @@ public class DatabaseImpl implements Database
// no access version, fall back to "generic"
accessVersion = null;
}
-
+
_fileFormat = possibleFileFormats.get(accessVersion);
-
+
if(_fileFormat == null) {
throw new IllegalStateException(withErrorContext(
"Could not determine FileFormat"));
@@ -802,7 +802,7 @@ public class DatabaseImpl implements Database
// just need one shared buffer
_buffer = buffer;
}
-
+
/**
* @return the currently configured database default language sort order for
* textual columns
@@ -843,7 +843,7 @@ public class DatabaseImpl implements Database
releaseSharedBuffer(buffer);
}
}
-
+
/**
* @return a PropertyMaps instance decoded from the given bytes (always
* returns non-{@code null} result).
@@ -851,11 +851,11 @@ public class DatabaseImpl implements Database
*/
public PropertyMaps readProperties(byte[] propsBytes, int objectId,
RowIdImpl rowId)
- throws IOException
+ throws IOException
{
- return getPropsHandler().read(propsBytes, objectId, rowId);
+ return getPropsHandler().read(propsBytes, objectId, rowId, null);
}
-
+
/**
* Read the system catalog
*/
@@ -882,10 +882,10 @@ public class DatabaseImpl implements Database
.toCursor());
}
- _tableParentId = _tableFinder.findObjectId(DB_PARENT_ID,
+ _tableParentId = _tableFinder.findObjectId(DB_PARENT_ID,
SYSTEM_OBJECT_NAME_TABLES);
- if(_tableParentId == null) {
+ if(_tableParentId == null) {
throw new IOException(withErrorContext(
"Did not find required parent table id"));
}
@@ -895,7 +895,7 @@ public class DatabaseImpl implements Database
"Finished reading system catalog. Tables: " + getTableNames()));
}
}
-
+
public Set<String> getTableNames() throws IOException {
if(_tableNames == null) {
_tableNames = getTableNames(true, false, true);
@@ -938,14 +938,14 @@ public class DatabaseImpl implements Database
public TableIterableBuilder newIterable() {
return new TableIterableBuilder(this);
}
-
+
public TableImpl getTable(String name) throws IOException {
return getTable(name, false);
}
public TableMetaData getTableMetaData(String name) throws IOException {
return getTableInfo(name, true);
- }
+ }
/**
* @param tableDefPageNumber the page number of a table definition
@@ -959,7 +959,7 @@ public class DatabaseImpl implements Database
if(table != null) {
return table;
}
-
+
// lookup table info from system catalog
Row objectRow = _tableFinder.getObjectRow(
tableDefPageNumber, SYSTEM_CATALOG_COLUMNS);
@@ -978,19 +978,19 @@ public class DatabaseImpl implements Database
* @param includeSystemTables whether to consider returning a system table
* @return The table, or null if it doesn't exist
*/
- protected TableImpl getTable(String name, boolean includeSystemTables)
- throws IOException
+ protected TableImpl getTable(String name, boolean includeSystemTables)
+ throws IOException
{
TableInfo tableInfo = getTableInfo(name, includeSystemTables);
- return ((tableInfo != null) ?
+ return ((tableInfo != null) ?
getTable(tableInfo, includeSystemTables) : null);
}
- private TableInfo getTableInfo(String name, boolean includeSystemTables)
- throws IOException
+ private TableInfo getTableInfo(String name, boolean includeSystemTables)
+ throws IOException
{
TableInfo tableInfo = lookupTable(name);
-
+
if ((tableInfo == null) || (tableInfo.pageNumber == null)) {
return null;
}
@@ -1001,8 +1001,8 @@ public class DatabaseImpl implements Database
return tableInfo;
}
- private TableImpl getTable(TableInfo tableInfo, boolean includeSystemTables)
- throws IOException
+ private TableImpl getTable(TableInfo tableInfo, boolean includeSystemTables)
+ throws IOException
{
if(tableInfo.isLinked()) {
@@ -1017,15 +1017,15 @@ public class DatabaseImpl implements Database
linkedDb = getLinkResolver().resolveLinkedDatabase(this, linkedDbName);
_linkedDbs.put(linkedDbName, linkedDb);
}
-
- return ((DatabaseImpl)linkedDb).getTable(linkedTableName,
+
+ return ((DatabaseImpl)linkedDb).getTable(linkedTableName,
includeSystemTables);
}
return readTable(tableInfo.tableName, tableInfo.pageNumber,
tableInfo.flags);
}
-
+
/**
* Create a new table in this database
* @param name Name of the table to create
@@ -1057,28 +1057,28 @@ public class DatabaseImpl implements Database
.toTable(this);
}
- public void createLinkedTable(String name, String linkedDbName,
+ public void createLinkedTable(String name, String linkedDbName,
String linkedTableName)
throws IOException
{
if(lookupTable(name) != null) {
throw new IllegalArgumentException(withErrorContext(
- "Cannot create linked table with name of existing table '" + name +
+ "Cannot create linked table with name of existing table '" + name +
"'"));
}
validateIdentifierName(name, getFormat().MAX_TABLE_NAME_LENGTH, "table");
- validateName(linkedDbName, DataType.MEMO.getMaxSize(),
+ validateName(linkedDbName, DataType.MEMO.getMaxSize(),
"linked database");
- validateIdentifierName(linkedTableName, getFormat().MAX_TABLE_NAME_LENGTH,
+ validateIdentifierName(linkedTableName, getFormat().MAX_TABLE_NAME_LENGTH,
"linked table");
getPageChannel().startWrite();
try {
-
+
int linkedTableId = _tableFinder.getNextFreeSyntheticId();
- addNewTable(name, linkedTableId, TYPE_LINKED_TABLE, linkedDbName,
+ addNewTable(name, linkedTableId, TYPE_LINKED_TABLE, linkedDbName,
linkedTableName);
} finally {
@@ -1089,16 +1089,16 @@ public class DatabaseImpl implements Database
/**
* Adds a newly created table to the relevant internal database structures.
*/
- void addNewTable(String name, int tdefPageNumber, Short type,
- String linkedDbName, String linkedTableName)
- throws IOException
+ void addNewTable(String name, int tdefPageNumber, Short type,
+ String linkedDbName, String linkedTableName)
+ throws IOException
{
//Add this table to our internal list.
addTable(name, Integer.valueOf(tdefPageNumber), type, linkedDbName,
linkedTableName);
-
+
//Add this table to system tables
- addToSystemCatalog(name, tdefPageNumber, type, linkedDbName,
+ addToSystemCatalog(name, tdefPageNumber, type, linkedDbName,
linkedTableName, _tableParentId);
addToAccessControlEntries(tdefPageNumber, _tableParentId, _newTableSIDs);
}
@@ -1126,7 +1126,7 @@ public class DatabaseImpl implements Database
table1 = table2;
table2 = tmp;
}
-
+
return getRelationshipsImpl(table1, table2, true);
}
@@ -1140,25 +1140,25 @@ public class DatabaseImpl implements Database
// all tables
return getRelationshipsImpl((TableImpl)table, null, true);
}
-
+
public List<Relationship> getRelationships()
throws IOException
{
return getRelationshipsImpl(null, null, false);
}
-
+
public List<Relationship> getSystemRelationships()
throws IOException
{
return getRelationshipsImpl(null, null, true);
}
-
+
private List<Relationship> getRelationshipsImpl(
TableImpl table1, TableImpl table2, boolean includeSystemTables)
throws IOException
{
initRelationships();
-
+
List<Relationship> relationships = new ArrayList<Relationship>();
if(table1 != null) {
@@ -1174,15 +1174,15 @@ public class DatabaseImpl implements Database
collectRelationships(new CursorBuilder(_relationships).toCursor(),
null, null, relationships, includeSystemTables);
}
-
+
return relationships;
}
- RelationshipImpl writeRelationship(RelationshipCreator creator)
+ RelationshipImpl writeRelationship(RelationshipCreator creator)
throws IOException
{
initRelationships();
-
+
String name = createRelationshipName(creator);
RelationshipImpl newRel = creator.createRelationshipImpl(name);
@@ -1212,13 +1212,13 @@ public class DatabaseImpl implements Database
getPageChannel().startWrite();
try {
-
+
int relObjId = _tableFinder.getNextFreeSyntheticId();
_relationships.addRows(rows);
addToSystemCatalog(name, relObjId, TYPE_RELATIONSHIP, null, null,
_relParentId);
addToAccessControlEntries(relObjId, _relParentId, _newRelSIDs);
-
+
} finally {
getPageChannel().finishWrite();
}
@@ -1230,14 +1230,14 @@ public class DatabaseImpl implements Database
// the relationships table does not get loaded until first accessed
if(_relationships == null) {
// need the parent id of the relationships objects
- _relParentId = _tableFinder.findObjectId(DB_PARENT_ID,
+ _relParentId = _tableFinder.findObjectId(DB_PARENT_ID,
SYSTEM_OBJECT_NAME_RELATIONSHIPS);
_relationships = getRequiredSystemTable(TABLE_SYSTEM_RELATIONSHIPS);
}
}
private String createRelationshipName(RelationshipCreator creator)
- throws IOException
+ throws IOException
{
// ensure that the final identifier name does not get too long
// - the primary name is limited to ((max / 2) - 3)
@@ -1259,7 +1259,7 @@ public class DatabaseImpl implements Database
// now ensure name is unique
Set<String> names = new HashSet<String>();
-
+
// collect the names of all relationships for uniqueness check
for(Row row :
CursorImpl.createCursor(_systemCatalog).newIterable().setColumnNames(
@@ -1276,7 +1276,7 @@ public class DatabaseImpl implements Database
// check those names as well
for(Index idx : creator.getSecondaryTable().getIndexes()) {
names.add(toLookupName(idx.getName()));
- }
+ }
}
String baseName = toLookupName(origName);
@@ -1288,7 +1288,7 @@ public class DatabaseImpl implements Database
return ((i == 0) ? origName : (origName + i));
}
-
+
public List<Query> getQueries() throws IOException
{
// the queries table does not get loaded until first accessed
@@ -1298,7 +1298,7 @@ public class DatabaseImpl implements Database
// find all the queries from the system catalog
List<Row> queryInfo = new ArrayList<Row>();
- Map<Integer,List<QueryImpl.Row>> queryRowMap =
+ Map<Integer,List<QueryImpl.Row>> queryRowMap =
new HashMap<Integer,List<QueryImpl.Row>>();
for(Row row :
CursorImpl.createCursor(_systemCatalog).newIterable().setColumnNames(
@@ -1346,10 +1346,10 @@ public class DatabaseImpl implements Database
private TableImpl getRequiredSystemTable(String tableName) throws IOException
{
TableImpl table = getSystemTable(tableName);
- if(table == null) {
+ if(table == null) {
throw new IOException(withErrorContext(
"Could not find system table " + tableName));
- }
+ }
return table;
}
@@ -1378,26 +1378,21 @@ public class DatabaseImpl implements Database
* @return the PropertyMaps for the object with the given id
* @usage _advanced_method_
*/
- public PropertyMaps getPropertiesForObject(int objectId)
+ public PropertyMaps getPropertiesForObject(
+ int objectId, PropertyMaps.Owner owner)
throws IOException
{
- Row objectRow = _tableFinder.getObjectRow(
- objectId, SYSTEM_CATALOG_PROPS_COLUMNS);
- byte[] propsBytes = null;
- RowIdImpl rowId = null;
- if(objectRow != null) {
- propsBytes = objectRow.getBytes(CAT_COL_PROPS);
- rowId = (RowIdImpl)objectRow.getId();
- }
- return readProperties(propsBytes, objectId, rowId);
+ return readProperties(
+ objectId, _tableFinder.getObjectRow(
+ objectId, SYSTEM_CATALOG_PROPS_COLUMNS), owner);
}
private Integer getDbParentId() throws IOException {
if(_dbParentId == null) {
// need the parent id of the databases objects
- _dbParentId = _tableFinder.findObjectId(DB_PARENT_ID,
+ _dbParentId = _tableFinder.findObjectId(DB_PARENT_ID,
SYSTEM_OBJECT_NAME_DATABASES);
- if(_dbParentId == null) {
+ if(_dbParentId == null) {
throw new IOException(withErrorContext(
"Did not find required parent db id"));
}
@@ -1418,7 +1413,7 @@ public class DatabaseImpl implements Database
if(msysDbRow != null) {
owner = msysDbRow.getBytes(CAT_COL_OWNER);
}
- _newObjOwner = (((owner != null) && (owner.length > 0)) ?
+ _newObjOwner = (((owner != null) && (owner.length > 0)) ?
owner : SYS_DEFAULT_SID);
}
return _newObjOwner;
@@ -1430,17 +1425,23 @@ public class DatabaseImpl implements Database
private PropertyMaps getPropertiesForDbObject(String dbName)
throws IOException
{
- Row objectRow = _tableFinder.getObjectRow(
- getDbParentId(), dbName, SYSTEM_CATALOG_PROPS_COLUMNS);
+ return readProperties(
+ -1, _tableFinder.getObjectRow(
+ getDbParentId(), dbName, SYSTEM_CATALOG_PROPS_COLUMNS), null);
+ }
+
+ private PropertyMaps readProperties(int objectId, Row objectRow,
+ PropertyMaps.Owner owner)
+ throws IOException
+ {
byte[] propsBytes = null;
- int objectId = -1;
RowIdImpl rowId = null;
if(objectRow != null) {
propsBytes = objectRow.getBytes(CAT_COL_PROPS);
objectId = objectRow.getInt(CAT_COL_ID);
rowId = (RowIdImpl)objectRow.getId();
}
- return readProperties(propsBytes, objectId, rowId);
+ return getPropsHandler().read(propsBytes, objectId, rowId, owner);
}
public String getDatabasePassword() throws IOException
@@ -1462,7 +1463,7 @@ public class DatabaseImpl implements Database
pwdBytes[i] ^= pwdMask[i % pwdMask.length];
}
}
-
+
boolean hasPassword = false;
for(int i = 0; i < pwdBytes.length; ++i) {
if(pwdBytes[i] != 0) {
@@ -1504,14 +1505,14 @@ public class DatabaseImpl implements Database
for(Row row : cursor) {
String fromName = row.getString(REL_COL_FROM_TABLE);
String toName = row.getString(REL_COL_TO_TABLE);
-
- if(((fromTableName == null) ||
+
+ if(((fromTableName == null) ||
fromTableName.equalsIgnoreCase(fromName)) &&
- ((toTableName == null) ||
+ ((toTableName == null) ||
toTableName.equalsIgnoreCase(toName))) {
String relName = row.getString(REL_COL_NAME);
-
+
// found more info for a relationship. see if we already have some
// info for this relationship
Relationship rel = null;
@@ -1558,15 +1559,15 @@ public class DatabaseImpl implements Database
rel.getFromColumns().set(colIdx, fromCol);
rel.getToColumns().set(colIdx, toCol);
}
- }
+ }
}
-
+
/**
* Add a new table to the system catalog
* @param name Table name
* @param objectId id of the new object
*/
- private void addToSystemCatalog(String name, int objectId, Short type,
+ private void addToSystemCatalog(String name, int objectId, Short type,
String linkedDbName, String linkedTableName,
Integer parentId)
throws IOException
@@ -1607,8 +1608,8 @@ public class DatabaseImpl implements Database
* Adds a new object to the system's access control entries
*/
private void addToAccessControlEntries(
- Integer objectId, Integer parentId, List<byte[]> sids)
- throws IOException
+ Integer objectId, Integer parentId, List<byte[]> sids)
+ throws IOException
{
if(sids.isEmpty()) {
collectNewObjectSIDs(parentId, sids);
@@ -1630,20 +1631,20 @@ public class DatabaseImpl implements Database
sidCol.setRowValue(aceRow, sid);
aceRows.add(aceRow);
}
- acEntries.addRows(aceRows);
+ acEntries.addRows(aceRows);
}
/**
* Find collection of SIDs for the given parent id.
*/
- private void collectNewObjectSIDs(Integer parentId, List<byte[]> sids)
+ private void collectNewObjectSIDs(Integer parentId, List<byte[]> sids)
throws IOException
{
// search for ACEs matching the given parentId. use the index on the
// objectId column if found (should be there)
Cursor cursor = createCursorWithOptionalIndex(
getAccessControlEntries(), ACE_COL_OBJECT_ID, parentId);
-
+
for(Row row : cursor) {
Integer objId = row.getInt(ACE_COL_OBJECT_ID);
if(parentId.equals(objId)) {
@@ -1668,7 +1669,7 @@ public class DatabaseImpl implements Database
if(table != null) {
return table;
}
-
+
ByteBuffer buffer = takeSharedBuffer();
try {
// need to load table from db
@@ -1703,12 +1704,12 @@ public class DatabaseImpl implements Database
if(LOG.isDebugEnabled()) {
LOG.debug(withErrorContext(
"Could not find expected index on table " + table.getName()));
- }
+ }
}
// use table scan instead
return CursorImpl.createCursor(table);
}
-
+
public void flush() throws IOException {
if(_linkedDbs != null) {
for(Database linkedDb : _linkedDbs.values()) {
@@ -1717,7 +1718,7 @@ public class DatabaseImpl implements Database
}
_pageChannel.flush();
}
-
+
public void close() throws IOException {
if(_linkedDbs != null) {
for(Database linkedDb : _linkedDbs.values()) {
@@ -1735,7 +1736,7 @@ public class DatabaseImpl implements Database
"Cannot create table with name of existing table '" + name + "'"));
}
}
-
+
/**
* Validates an identifier name.
*
@@ -1747,7 +1748,7 @@ public class DatabaseImpl implements Database
* <li>Can't begin with leading spaces.</li>
* <li>Can't include control characters (ASCII values 0 through 31).</li>
* </ul>
- *
+ *
* @usage _advanced_method_
*/
public static void validateIdentifierName(String name,
@@ -1794,7 +1795,7 @@ public class DatabaseImpl implements Database
public static boolean isBlank(String name) {
return((name == null) || (name.trim().length() == 0));
}
-
+
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
@@ -1803,11 +1804,11 @@ public class DatabaseImpl implements Database
/**
* Adds a table to the _tableLookup and resets the _tableNames set
*/
- private void addTable(String tableName, Integer pageNumber, Short type,
+ private void addTable(String tableName, Integer pageNumber, Short type,
String linkedDbName, String linkedTableName)
{
_tableLookup.put(toLookupName(tableName),
- createTableInfo(tableName, pageNumber, 0, type,
+ createTableInfo(tableName, pageNumber, 0, type,
linkedDbName, linkedTableName));
// clear this, will be created next time needed
_tableNames = null;
@@ -1817,7 +1818,7 @@ public class DatabaseImpl implements Database
* Creates a TableInfo instance appropriate for the given table data.
*/
private static TableInfo createTableInfo(
- String tableName, Integer pageNumber, int flags, Short type,
+ String tableName, Integer pageNumber, int flags, Short type,
String linkedDbName, String linkedTableName)
{
if(TYPE_LINKED_TABLE.equals(type)) {
@@ -1883,7 +1884,7 @@ public class DatabaseImpl implements Database
// use system default
return TimeZone.getDefault();
}
-
+
/**
* Returns the default Charset for the given JetFormat. This may or may not
* be platform specific, depending on the format, but can be overridden
@@ -1906,7 +1907,7 @@ public class DatabaseImpl implements Database
// use format default
return format.CHARSET;
}
-
+
/**
* Returns the default Table.ColumnOrder. This defaults to
* {@link Database#DEFAULT_COLUMN_ORDER}, but can be overridden using the system
@@ -1926,7 +1927,7 @@ public class DatabaseImpl implements Database
// use default order
return DEFAULT_COLUMN_ORDER;
}
-
+
/**
* Returns the default enforce foreign-keys policy. This defaults to
* {@code true}, but can be overridden using the system
@@ -1941,7 +1942,7 @@ public class DatabaseImpl implements Database
}
return true;
}
-
+
/**
* Returns the default allow auto number insert policy. This defaults to
* {@code false}, but can be overridden using the system
@@ -1956,7 +1957,7 @@ public class DatabaseImpl implements Database
}
return false;
}
-
+
/**
* Copies the given db InputStream to the given channel using the most
* efficient means possible.
@@ -1967,7 +1968,7 @@ public class DatabaseImpl implements Database
ReadableByteChannel readChannel = Channels.newChannel(in);
if(!BROKEN_NIO) {
// sane implementation
- channel.transferFrom(readChannel, 0, MAX_EMPTYDB_SIZE);
+ channel.transferFrom(readChannel, 0, MAX_EMPTYDB_SIZE);
} else {
// do things the hard way for broken vms
ByteBuffer bb = ByteBuffer.allocate(8096);
@@ -2006,12 +2007,12 @@ public class DatabaseImpl implements Database
{
InputStream stream = DatabaseImpl.class.getClassLoader()
.getResourceAsStream(resourceName);
-
+
if(stream == null) {
-
+
stream = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(resourceName);
-
+
if(stream == null) {
throw new IOException("Could not load jackcess resource " +
resourceName);
@@ -2032,8 +2033,8 @@ public class DatabaseImpl implements Database
private static void addFileFormatDetails(
FileFormat fileFormat, String emptyFileName, JetFormat format)
{
- String emptyFile =
- ((emptyFileName != null) ?
+ String emptyFile =
+ ((emptyFileName != null) ?
RESOURCE_PATH + emptyFileName + fileFormat.getFileExtension() : null);
FILE_FORMAT_DETAILS.put(fileFormat, new FileFormatDetails(emptyFile, format));
}
@@ -2041,7 +2042,7 @@ public class DatabaseImpl implements Database
private static String getName(File file) {
if(file == null) {
return "<UNKNOWN.DB>";
- }
+ }
return file.getName();
}
@@ -2071,14 +2072,14 @@ public class DatabaseImpl implements Database
public String getName() {
return tableName;
}
-
+
public boolean isLinked() {
return false;
}
public boolean isSystem() {
return isSystemObject(flags);
- }
+ }
public String getLinkedTableName() {
return null;
@@ -2116,8 +2117,8 @@ public class DatabaseImpl implements Database
private final String linkedDbName;
private final String linkedTableName;
- private LinkedTableInfo(Integer newPageNumber, String newTableName,
- int newFlags, String newLinkedDbName,
+ private LinkedTableInfo(Integer newPageNumber, String newTableName,
+ int newFlags, String newLinkedDbName,
String newLinkedTableName) {
super(newPageNumber, newTableName, newFlags);
linkedDbName = newLinkedDbName;
@@ -2176,11 +2177,11 @@ public class DatabaseImpl implements Database
*/
private abstract class TableFinder
{
- public Integer findObjectId(Integer parentId, String name)
- throws IOException
+ public Integer findObjectId(Integer parentId, String name)
+ throws IOException
{
Cursor cur = findRow(parentId, name);
- if(cur == null) {
+ if(cur == null) {
return null;
}
ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
@@ -2188,8 +2189,8 @@ public class DatabaseImpl implements Database
}
public Row getObjectRow(Integer parentId, String name,
- Collection<String> columns)
- throws IOException
+ Collection<String> columns)
+ throws IOException
{
Cursor cur = findRow(parentId, name);
return ((cur != null) ? cur.getCurrentRow(columns) : null);
@@ -2246,7 +2247,7 @@ public class DatabaseImpl implements Database
if(TYPE_LINKED_TABLE.equals(type) &&
matchesLinkedTable(table, linkedTableName, linkedDbName)) {
return true;
- }
+ }
}
return false;
}
@@ -2254,7 +2255,7 @@ public class DatabaseImpl implements Database
protected abstract Cursor findRow(Integer parentId, String name)
throws IOException;
- protected abstract Cursor findRow(Integer objectId)
+ protected abstract Cursor findRow(Integer objectId)
throws IOException;
protected abstract Cursor getTableNamesCursor() throws IOException;
@@ -2287,7 +2288,7 @@ public class DatabaseImpl implements Database
private DefaultTableFinder(IndexCursor systemCatalogCursor) {
_systemCatalogCursor = systemCatalogCursor;
}
-
+
private void initIdCursor() throws IOException {
if(_systemCatalogIdCursor == null) {
_systemCatalogIdCursor = _systemCatalog.newCursor()
@@ -2297,15 +2298,15 @@ public class DatabaseImpl implements Database
}
@Override
- protected Cursor findRow(Integer parentId, String name)
- throws IOException
+ protected Cursor findRow(Integer parentId, String name)
+ throws IOException
{
return (_systemCatalogCursor.findFirstRowByEntry(parentId, name) ?
_systemCatalogCursor : null);
}
@Override
- protected Cursor findRow(Integer objectId) throws IOException
+ protected Cursor findRow(Integer objectId) throws IOException
{
initIdCursor();
return (_systemCatalogIdCursor.findFirstRowByEntry(objectId) ?
@@ -2336,7 +2337,7 @@ public class DatabaseImpl implements Database
return createTableInfo(realName, pageNumber, flags, type, linkedDbName,
linkedTableName);
}
-
+
@Override
protected Cursor getTableNamesCursor() throws IOException {
return _systemCatalogCursor.getIndex().newCursor()
@@ -2360,7 +2361,7 @@ public class DatabaseImpl implements Database
return (Integer)_systemCatalogIdCursor.getCurrentRowValue(idCol);
}
}
-
+
/**
* Fallback table lookup handler, using catalog table scans.
*/
@@ -2373,18 +2374,18 @@ public class DatabaseImpl implements Database
}
@Override
- protected Cursor findRow(Integer parentId, String name)
- throws IOException
+ protected Cursor findRow(Integer parentId, String name)
+ throws IOException
{
Map<String,Object> rowPat = new HashMap<String,Object>();
- rowPat.put(CAT_COL_PARENT_ID, parentId);
+ rowPat.put(CAT_COL_PARENT_ID, parentId);
rowPat.put(CAT_COL_NAME, name);
return (_systemCatalogCursor.findFirstRow(rowPat) ?
_systemCatalogCursor : null);
}
@Override
- protected Cursor findRow(Integer objectId) throws IOException
+ protected Cursor findRow(Integer objectId) throws IOException
{
ColumnImpl idCol = _systemCatalog.getColumn(CAT_COL_ID);
return (_systemCatalogCursor.findFirstRow(idCol, objectId) ?
@@ -2423,7 +2424,7 @@ public class DatabaseImpl implements Database
return null;
}
-
+
@Override
protected Cursor getTableNamesCursor() throws IOException {
return _systemCatalogCursor;
@@ -2453,7 +2454,7 @@ public class DatabaseImpl implements Database
{
private final Integer _pageNumber;
- private WeakTableReference(Integer pageNumber, TableImpl table,
+ private WeakTableReference(Integer pageNumber, TableImpl table,
ReferenceQueue<TableImpl> queue) {
super(table, queue);
_pageNumber = pageNumber;
@@ -2469,9 +2470,9 @@ public class DatabaseImpl implements Database
*/
private static final class TableCache
{
- private final Map<Integer,WeakTableReference> _tables =
+ private final Map<Integer,WeakTableReference> _tables =
new HashMap<Integer,WeakTableReference>();
- private final ReferenceQueue<TableImpl> _queue =
+ private final ReferenceQueue<TableImpl> _queue =
new ReferenceQueue<TableImpl>();
public TableImpl get(Integer pageNumber) {
@@ -2481,7 +2482,7 @@ public class DatabaseImpl implements Database
public TableImpl put(TableImpl table) {
purgeOldRefs();
-
+
Integer pageNumber = table.getTableDefPageNumber();
WeakTableReference ref = new WeakTableReference(
pageNumber, table, _queue);
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/InternalColumnValidator.java b/src/main/java/com/healthmarketscience/jackcess/impl/InternalColumnValidator.java
new file mode 100644
index 0000000..0b4199b
--- /dev/null
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/InternalColumnValidator.java
@@ -0,0 +1,62 @@
+/*
+Copyright (c) 2018 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.impl;
+
+import java.io.IOException;
+
+import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.util.ColumnValidator;
+
+/**
+ * Base class for ColumnValidator instances handling "internal" validation
+ * functionality, which are wrappers around any "external" behavior.
+ *
+ * @author James Ahlborn
+ */
+abstract class InternalColumnValidator implements ColumnValidator
+{
+ private ColumnValidator _delegate;
+
+ protected InternalColumnValidator(ColumnValidator delegate)
+ {
+ _delegate = delegate;
+ }
+
+ ColumnValidator getExternal() {
+ ColumnValidator extValidator = _delegate;
+ while(extValidator instanceof InternalColumnValidator) {
+ extValidator = ((InternalColumnValidator)extValidator)._delegate;
+ }
+ return extValidator;
+ }
+
+ void setExternal(ColumnValidator extValidator) {
+ InternalColumnValidator intValidator = this;
+ while(intValidator._delegate instanceof InternalColumnValidator) {
+ intValidator = (InternalColumnValidator)intValidator._delegate;
+ }
+ intValidator._delegate = extValidator;
+ }
+
+ public final Object validate(Column col, Object val) throws IOException {
+ val = _delegate.validate(col, val);
+ return internalValidate(col, val);
+ }
+
+ protected abstract Object internalValidate(Column col, Object val)
+ throws IOException;
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/LongValueColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/LongValueColumnImpl.java
index 73648b3..f878562 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/LongValueColumnImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/LongValueColumnImpl.java
@@ -17,19 +17,19 @@ limitations under the License.
package com.healthmarketscience.jackcess.impl;
import java.io.IOException;
-import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Collection;
+import com.healthmarketscience.jackcess.InvalidValueException;
/**
* ColumnImpl subclass which is used for long value data types.
- *
+ *
* @author James Ahlborn
* @usage _advanced_class_
*/
-class LongValueColumnImpl extends ColumnImpl
+class LongValueColumnImpl extends ColumnImpl
{
/**
* Long value (LVAL) type that indicates that the value is stored on the
@@ -60,12 +60,12 @@ class LongValueColumnImpl extends ColumnImpl
{
super(args);
}
-
+
@Override
public int getOwnedPageCount() {
return ((_lvalBufferH == null) ? 0 : _lvalBufferH.getOwnedPageCount());
}
-
+
@Override
void setUsageMaps(UsageMap ownedPages, UsageMap freeSpacePages) {
_lvalBufferH = new UmapLongValueBufferHolder(ownedPages, freeSpacePages);
@@ -75,7 +75,7 @@ class LongValueColumnImpl extends ColumnImpl
void collectUsageMapPages(Collection<Integer> pages) {
_lvalBufferH.collectUsageMapPages(pages);
}
-
+
@Override
void postTableLoadInit() throws IOException {
if(_lvalBufferH == null) {
@@ -104,7 +104,7 @@ class LongValueColumnImpl extends ColumnImpl
default:
throw new RuntimeException(withErrorContext(
"unexpected var length, long value type: " + getType()));
- }
+ }
}
@Override
@@ -122,12 +122,12 @@ class LongValueColumnImpl extends ColumnImpl
default:
throw new RuntimeException(withErrorContext(
"unexpected var length, long value type: " + getType()));
- }
+ }
// create long value buffer
return writeLongValue(toByteArray(obj), remainingRowLength);
- }
-
+ }
+
/**
* @param lvalDefinition Column value that points to an LVAL record
* @return The LVAL data
@@ -152,7 +152,7 @@ class LongValueColumnImpl extends ColumnImpl
if(rowLen < length) {
// warn the caller, but return whatever we can
LOG.warn(withErrorContext(
- "Value may be truncated: expected length " +
+ "Value may be truncated: expected length " +
length + " found " + rowLen));
rtn = new byte[rowLen];
}
@@ -172,7 +172,7 @@ class LongValueColumnImpl extends ColumnImpl
int rowNum = ByteUtil.getUnsignedByte(def);
int pageNum = ByteUtil.get3ByteInt(def, def.position());
ByteBuffer lvalPage = getPageChannel().createPageBuffer();
-
+
switch (type) {
case LONG_VALUE_TYPE_OTHER_PAGE:
{
@@ -185,16 +185,16 @@ class LongValueColumnImpl extends ColumnImpl
if(rowLen < length) {
// warn the caller, but return whatever we can
LOG.warn(withErrorContext(
- "Value may be truncated: expected length " +
+ "Value may be truncated: expected length " +
length + " found " + rowLen));
rtn = new byte[rowLen];
}
-
+
lvalPage.position(rowStart);
lvalPage.get(rtn);
}
break;
-
+
case LONG_VALUE_TYPE_OTHER_PAGES:
ByteBuffer rtnBuf = ByteBuffer.wrap(rtn);
@@ -205,7 +205,7 @@ class LongValueColumnImpl extends ColumnImpl
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);
@@ -218,22 +218,22 @@ class LongValueColumnImpl extends ColumnImpl
chunkLength = remainingLen;
}
remainingLen -= chunkLength;
-
+
lvalPage.limit(rowEnd);
rtnBuf.put(lvalPage);
}
-
+
break;
-
+
default:
throw new IOException(withErrorContext(
"Unrecognized long value type: " + type));
}
}
-
+
return rtn;
}
-
+
/**
* @param lvalDefinition Column value that points to an LVAL record
* @return The LVAL data
@@ -259,11 +259,11 @@ class LongValueColumnImpl extends ColumnImpl
* value (unless written to other pages)
* @usage _advanced_method_
*/
- protected ByteBuffer writeLongValue(byte[] value, int remainingRowLength)
+ protected ByteBuffer writeLongValue(byte[] value, int remainingRowLength)
throws IOException
{
if(value.length > getType().getMaxSize()) {
- throw new IOException(withErrorContext(
+ throw new InvalidValueException(withErrorContext(
"value too big for column, max " +
getType().getMaxSize() + ", got " + value.length));
}
@@ -292,11 +292,11 @@ class LongValueColumnImpl extends ColumnImpl
def.putInt(0); //Unknown
def.put(value);
} else {
-
+
ByteBuffer lvalPage = null;
int firstLvalPageNum = PageChannel.INVALID_PAGE_NUMBER;
byte firstLvalRow = 0;
-
+
// write other page(s)
switch(type) {
case LONG_VALUE_TYPE_OTHER_PAGE:
@@ -335,7 +335,7 @@ class LongValueColumnImpl extends ColumnImpl
nextLvalPage = _lvalBufferH.getLongValuePage(
(remainingLen - chunkLength) + 4);
nextLvalPageNum = _lvalBufferH.getPageNumber();
- nextLvalRowNum = TableImpl.getRowsOnDataPage(nextLvalPage,
+ nextLvalRowNum = TableImpl.getRowsOnDataPage(nextLvalPage,
getFormat());
} else {
nextLvalPage = null;
@@ -345,7 +345,7 @@ class LongValueColumnImpl extends ColumnImpl
// add row to this page
TableImpl.addDataPageRow(lvalPage, chunkLength + 4, getFormat(), 0);
-
+
// write next page info
lvalPage.put((byte)nextLvalRowNum); // row number
ByteUtil.put3ByteInt(lvalPage, nextLvalPageNum); // page number
@@ -373,9 +373,9 @@ class LongValueColumnImpl extends ColumnImpl
def.put(firstLvalRow);
ByteUtil.put3ByteInt(def, firstLvalPageNum);
def.putInt(0); //Unknown
-
+
}
-
+
def.flip();
return def;
}
@@ -499,10 +499,10 @@ class LongValueColumnImpl extends ColumnImpl
@Override
protected ByteBuffer findNewPage(int dataLength) throws IOException {
- // grab last owned page and check for free space.
- ByteBuffer newPage = TableImpl.findFreeRowSpace(
+ // grab last owned page and check for free space.
+ ByteBuffer newPage = TableImpl.findFreeRowSpace(
_ownedPages, _freeSpacePages, _longValueBufferH);
-
+
if(newPage != null) {
if(TableImpl.rowFitsOnDataPage(dataLength, newPage, getFormat())) {
return newPage;
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java b/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java
index 4ac9e9a..a54ebb5 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java
@@ -46,16 +46,19 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
/** maps the PropertyMap name (case-insensitive) to the PropertyMap
instance */
- private final Map<String,PropertyMapImpl> _maps =
+ private final Map<String,PropertyMapImpl> _maps =
new LinkedHashMap<String,PropertyMapImpl>();
private final int _objectId;
private final RowIdImpl _rowId;
private final Handler _handler;
+ private final Owner _owner;
- public PropertyMaps(int objectId, RowIdImpl rowId, Handler handler) {
+ public PropertyMaps(int objectId, RowIdImpl rowId, Handler handler,
+ Owner owner) {
_objectId = objectId;
_rowId = rowId;
_handler = handler;
+ _owner = owner;
}
public int getObjectId() {
@@ -110,6 +113,9 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
public void save() throws IOException {
_handler.save(this);
+ if(_owner != null) {
+ _owner.propertiesUpdated();
+ }
}
@Override
@@ -129,7 +135,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
/** the system table "property" column */
private final ColumnImpl _propCol;
/** cache of PropColumns used to read/write property values */
- private final Map<DataType,PropColumn> _columns =
+ private final Map<DataType,PropColumn> _columns =
new HashMap<DataType,PropColumn>();
Handler(DatabaseImpl database) {
@@ -142,11 +148,11 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
* @return a PropertyMaps instance decoded from the given bytes (always
* returns non-{@code null} result).
*/
- public PropertyMaps read(byte[] propBytes, int objectId,
- RowIdImpl rowId)
- throws IOException
+ public PropertyMaps read(byte[] propBytes, int objectId,
+ RowIdImpl rowId, Owner owner)
+ throws IOException
{
- PropertyMaps maps = new PropertyMaps(objectId, rowId, this);
+ PropertyMaps maps = new PropertyMaps(objectId, rowId, this, owner);
if((propBytes == null) || (propBytes.length == 0)) {
return maps;
}
@@ -176,7 +182,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
short type = bb.getShort();
int endPos = bb.position() + len - 6;
- ByteBuffer bbBlock = PageChannel.narrowBuffer(bb, bb.position(),
+ ByteBuffer bbBlock = PageChannel.narrowBuffer(bb, bb.position(),
endPos);
if(type == PROPERTY_NAME_LIST) {
@@ -226,7 +232,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
writeBlock(propMap, propNames, propMap.getType(), bab);
}
}
-
+
return bab.toArray();
}
@@ -260,12 +266,12 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
writePropertyNames(propNames, bab);
} else {
writePropertyValues(propMap, propNames, bab);
- }
+ }
int len = bab.position() - blockStartPos;
bab.putInt(blockStartPos, len);
}
-
+
/**
* @return the property names parsed from the given data chunk
*/
@@ -281,7 +287,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
ByteArrayBuilder bab) {
for(String propName : propNames) {
writePropName(propName, bab);
- }
+ }
}
/**
@@ -290,7 +296,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
*/
private PropertyMapImpl readPropertyValues(
ByteBuffer bbBlock, List<String> propNames, short blockType,
- PropertyMaps maps)
+ PropertyMaps maps)
throws IOException
{
String mapName = DEFAULT_NAME;
@@ -305,13 +311,13 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
}
bbBlock.position(endPos);
}
-
+
PropertyMapImpl map = maps.get(mapName, blockType);
// read the values
while(bbBlock.hasRemaining()) {
- int valLen = bbBlock.getShort();
+ int valLen = bbBlock.getShort();
int endPos = bbBlock.position() + valLen - 2;
boolean isDdl = (bbBlock.get() != 0);
DataType dataType = DataType.fromByte(bbBlock.get());
@@ -333,9 +339,9 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
}
private void writePropertyValues(
- PropertyMapImpl propMap, Set<String> propNames, ByteArrayBuilder bab)
+ PropertyMapImpl propMap, Set<String> propNames, ByteArrayBuilder bab)
throws IOException
- {
+ {
// write the map name, if any
String mapName = propMap.getName();
int blockStartPos = bab.position();
@@ -384,7 +390,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
/**
* Reads a property name from the given data block
*/
- private String readPropName(ByteBuffer buffer) {
+ private String readPropName(ByteBuffer buffer) {
int nameLength = buffer.getShort();
byte[] nameBytes = ByteUtil.getBytes(buffer, nameLength);
return ColumnImpl.decodeUncompressedText(nameBytes, _database.getCharset());
@@ -404,8 +410,8 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
* Gets a PropColumn capable of reading/writing a property of the given
* DataType
*/
- private PropColumn getColumn(DataType dataType, String propName,
- int dataSize, Object value)
+ private PropColumn getColumn(DataType dataType, String propName,
+ int dataSize, Object value)
throws IOException
{
@@ -426,7 +432,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
}
// create column with ability to read/write the given data type
- col = ((colType == DataType.BOOLEAN) ?
+ col = ((colType == DataType.BOOLEAN) ?
new BooleanPropColumn() : new PropColumn(colType));
_columns.put(dataType, col);
@@ -436,11 +442,11 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
}
private static boolean isPseudoGuidColumn(
- DataType dataType, String propName, int dataSize, Object value)
+ DataType dataType, String propName, int dataSize, Object value)
throws IOException
{
// guids seem to be marked as "binary" fields
- return((dataType == DataType.BINARY) &&
+ return((dataType == DataType.BINARY) &&
((dataSize == DataType.GUID.getFixedSize()) ||
((dataSize == -1) && ColumnImpl.isGUIDValue(value))) &&
PropertyMap.GUID_PROP.equalsIgnoreCase(propName));
@@ -454,7 +460,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
private PropColumn(DataType type) {
super(null, null, type, 0, 0, 0);
}
-
+
@Override
public DatabaseImpl getDatabase() {
return _database;
@@ -487,4 +493,15 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
}
}
}
+
+ /**
+ * Utility interface for the object which owns the PropertyMaps
+ */
+ static interface Owner {
+
+ /**
+ * Invoked when new properties are saved.
+ */
+ public void propertiesUpdated() throws IOException;
+ }
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java
index aa824df..138b2c8 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java
@@ -43,6 +43,7 @@ import com.healthmarketscience.jackcess.ConstraintViolationException;
import com.healthmarketscience.jackcess.CursorBuilder;
import com.healthmarketscience.jackcess.Index;
import com.healthmarketscience.jackcess.IndexBuilder;
+import com.healthmarketscience.jackcess.InvalidValueException;
import com.healthmarketscience.jackcess.JackcessException;
import com.healthmarketscience.jackcess.PropertyMap;
import com.healthmarketscience.jackcess.Row;
@@ -57,18 +58,18 @@ import org.apache.commons.logging.LogFactory;
* A single database table
* <p>
* Is not thread-safe.
- *
+ *
* @author Tim McCune
* @usage _intermediate_class_
*/
-public class TableImpl implements Table
-{
+public class TableImpl implements Table, PropertyMaps.Owner
+{
private static final Log LOG = LogFactory.getLog(TableImpl.class);
private static final short OFFSET_MASK = (short)0x1FFF;
private static final short DELETED_ROW_MASK = (short)0x8000;
-
+
private static final short OVERFLOW_ROW_MASK = (short)0x4000;
static final int MAGIC_TABLE_NUMBER = 1625;
@@ -182,13 +183,13 @@ public class TableImpl implements Table
/** default cursor for iterating through the table, kept here for basic
table traversal */
private CursorImpl _defaultCursor;
-
+
/**
* Only used by unit tests
* @usage _advanced_method_
*/
- protected TableImpl(boolean testing, List<ColumnImpl> columns)
- throws IOException
+ protected TableImpl(boolean testing, List<ColumnImpl> columns)
+ throws IOException
{
if(!testing) {
throw new IllegalArgumentException();
@@ -215,7 +216,7 @@ public class TableImpl implements Table
_ownedPages = null;
_freeSpacePages = null;
}
-
+
/**
* @param database database which owns this table
* @param tableBuffer Buffer to read the table with
@@ -251,17 +252,17 @@ public class TableImpl implements Table
_ownedPages = UsageMap.read(getDatabase(), tableBuffer);
tableBuffer.position(getFormat().OFFSET_FREE_SPACE_PAGES);
_freeSpacePages = UsageMap.read(getDatabase(), tableBuffer);
-
+
for (int i = 0; i < _indexCount; i++) {
_indexDatas.add(IndexData.create(this, tableBuffer, i, getFormat()));
}
-
+
readColumnDefinitions(tableBuffer, columnCount);
readIndexDefinitions(tableBuffer);
// read column usage map info
- while((tableBuffer.remaining() >= 2) &&
+ while((tableBuffer.remaining() >= 2) &&
readColumnUsageMaps(tableBuffer)) {
// keep reading ...
}
@@ -283,7 +284,7 @@ public class TableImpl implements Table
// after fully constructed, allow column validator to be configured (but
// only for user tables)
for(ColumnImpl col : _columns) {
- col.setColumnValidator(null);
+ col.initColumnValidator();
}
}
}
@@ -306,15 +307,15 @@ public class TableImpl implements Table
public int getMaxColumnCount() {
return _maxColumnCount;
}
-
+
public int getColumnCount() {
return _columns.size();
}
-
+
public DatabaseImpl getDatabase() {
return _database;
}
-
+
/**
* @usage _advanced_method_
*/
@@ -336,7 +337,7 @@ public class TableImpl implements Table
public void setErrorHandler(ErrorHandler newErrorHandler) {
_tableErrorHandler = newErrorHandler;
- }
+ }
public int getTableDefPageNumber() {
return _tableDefPageNumber;
@@ -395,7 +396,7 @@ public class TableImpl implements Table
return count;
}
-
+
protected TempPageHolder getLongValueBuffer() {
return _longValueBufferH;
}
@@ -413,7 +414,7 @@ public class TableImpl implements Table
throw new IllegalArgumentException(withErrorContext(
"Column with name " + name + " does not exist in this table"));
}
-
+
public boolean hasColumn(String name) {
for(ColumnImpl column : _columns) {
if(column.getName().equalsIgnoreCase(name)) {
@@ -437,11 +438,17 @@ public class TableImpl implements Table
public PropertyMaps getPropertyMaps() throws IOException {
if(_propertyMaps == null) {
_propertyMaps = getDatabase().getPropertiesForObject(
- _tableDefPageNumber);
+ _tableDefPageNumber, this);
}
return _propertyMaps;
}
-
+
+ public void propertiesUpdated() throws IOException {
+ for(ColumnImpl col : _columns) {
+ col.propertiesUpdated();
+ }
+ }
+
public List<IndexImpl> getIndexes() {
return Collections.unmodifiableList(_indexes);
}
@@ -465,7 +472,7 @@ public class TableImpl implements Table
throw new IllegalArgumentException(withErrorContext(
"No primary key index found"));
}
-
+
public IndexImpl getForeignKeyIndex(Table otherTable) {
for(IndexImpl index : _indexes) {
if(index.isForeignKey() && (index.getReference() != null) &&
@@ -478,7 +485,7 @@ public class TableImpl implements Table
"No foreign key reference to " +
otherTable.getName() + " found"));
}
-
+
/**
* @return All of the IndexData on this table (unmodifiable List)
* @usage _advanced_method_
@@ -499,12 +506,12 @@ public class TableImpl implements Table
return _indexCount;
}
- public IndexImpl findIndexForColumns(Collection<String> searchColumns,
+ public IndexImpl findIndexForColumns(Collection<String> searchColumns,
IndexFeature feature) {
IndexImpl partialIndex = null;
for(IndexImpl index : _indexes) {
-
+
Collection<? extends Index.Column> indexColumns = index.getColumns();
if(indexColumns.size() < searchColumns.size()) {
continue;
@@ -525,14 +532,14 @@ public class TableImpl implements Table
}
if(searchMatches) {
-
- if(exactMatch && ((feature != IndexFeature.EXACT_UNIQUE_ONLY) ||
+
+ if(exactMatch && ((feature != IndexFeature.EXACT_UNIQUE_ONLY) ||
index.isUnique())) {
return index;
}
- if(!exactMatch && (feature == IndexFeature.ANY_MATCH) &&
- ((partialIndex == null) ||
+ if(!exactMatch && (feature == IndexFeature.ANY_MATCH) &&
+ ((partialIndex == null) ||
(indexColumns.size() < partialIndex.getColumnCount()))) {
// this is a better partial index match
partialIndex = index;
@@ -542,7 +549,7 @@ public class TableImpl implements Table
return partialIndex;
}
-
+
List<ColumnImpl> getAutoNumberColumns() {
return _autoNumColumns;
}
@@ -557,7 +564,7 @@ public class TableImpl implements Table
public CursorBuilder newCursor() {
return new CursorBuilder(this);
}
-
+
public void reset() {
getDefaultCursor().reset();
}
@@ -583,14 +590,14 @@ public class TableImpl implements Table
* Delete the row for the given rowId.
* @usage _advanced_method_
*/
- public void deleteRow(RowState rowState, RowIdImpl rowId)
- throws IOException
+ public void deleteRow(RowState rowState, RowIdImpl rowId)
+ throws IOException
{
requireValidRowId(rowId);
-
+
getPageChannel().startWrite();
try {
-
+
// ensure that the relevant row state is up-to-date
ByteBuffer rowBuffer = positionAtRowHeader(rowState, rowId);
@@ -599,7 +606,7 @@ public class TableImpl implements Table
return;
}
requireNonDeletedRow(rowState, rowId);
-
+
// delete flag always gets set in the "header" row (even if data is on
// overflow row)
int pageNumber = rowState.getHeaderRowId().getPageNumber();
@@ -620,7 +627,7 @@ public class TableImpl implements Table
rowValues = rowState.getRowCacheValues();
// check foreign keys before proceeding w/ deletion
- _fkEnforcer.deleteRow(rowValues);
+ _fkEnforcer.deleteRow(rowValues);
// move back to the header
rowBuffer = positionAtRowHeader(rowState, rowId);
@@ -636,7 +643,7 @@ public class TableImpl implements Table
for(IndexData indexData : _indexDatas) {
indexData.deleteRow(rowValues, rowId);
}
-
+
// make sure table def gets updated
updateTableDefinition(-1);
@@ -644,11 +651,11 @@ public class TableImpl implements Table
getPageChannel().finishWrite();
}
}
-
+
public Row getNextRow() throws IOException {
return getDefaultCursor().getNextRow();
}
-
+
/**
* Reads a single column from the given row.
* @usage _advanced_method_
@@ -662,11 +669,11 @@ public class TableImpl implements Table
"Given column " + column + " is not from this table"));
}
requireValidRowId(rowId);
-
+
// position at correct row
ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
requireNonDeletedRow(rowState, rowId);
-
+
return getRowColumn(getFormat(), rowBuffer, column, rowState, null);
}
@@ -711,7 +718,7 @@ public class TableImpl implements Table
}
return rtn;
}
-
+
/**
* Reads the column data from the given row buffer. Leaves limit unchanged.
* Caches the returned value in the rowState.
@@ -743,10 +750,10 @@ public class TableImpl implements Table
// we already have it, use it
return cachedValue;
}
-
+
// reset position to row start
rowBuffer.reset();
-
+
// locate the column data bytes
int rowStart = rowBuffer.position();
int colDataPos = 0;
@@ -757,9 +764,9 @@ public class TableImpl implements Table
int dataStart = rowStart + format.OFFSET_COLUMN_FIXED_DATA_ROW_OFFSET;
colDataPos = dataStart + column.getFixedDataOffset();
colDataLen = column.getType().getFixedSize(column.getLength());
-
+
} else {
- int varDataStart;
+ int varDataStart;
int varDataEnd;
if(format.SIZE_ROW_VAR_COL_OFFSET == 2) {
@@ -799,13 +806,13 @@ public class TableImpl implements Table
// to update the index on row deletion. note, most of the returned
// values are immutable, except for binary data (returned as byte[]),
// but binary data shouldn't be indexed anyway.
- return rowState.setRowCacheValue(column.getColumnIndex(),
+ return rowState.setRowCacheValue(column.getColumnIndex(),
column.read(columnData));
} catch(Exception e) {
// cache "raw" row value. see note about caching above
- rowState.setRowCacheValue(column.getColumnIndex(),
+ rowState.setRowCacheValue(column.getColumnIndex(),
ColumnImpl.rawDataWrapper(columnData));
return rowState.handleRowError(column, columnData, e);
@@ -814,7 +821,7 @@ public class TableImpl implements Table
private static short[] readJumpTableVarColOffsets(
RowState rowState, ByteBuffer rowBuffer, int rowStart,
- NullMask nullMask)
+ NullMask nullMask)
{
short[] varColOffsets = rowState.getVarColOffsets();
if(varColOffsets != null) {
@@ -824,14 +831,14 @@ public class TableImpl implements Table
// calculate offsets using jump-table info
int nullMaskSize = nullMask.byteSize();
int rowEnd = rowStart + rowBuffer.remaining() - 1;
- int numVarCols = ByteUtil.getUnsignedByte(rowBuffer,
+ int numVarCols = ByteUtil.getUnsignedByte(rowBuffer,
rowEnd - nullMaskSize);
varColOffsets = new short[numVarCols + 1];
-
+
int rowLen = rowEnd - rowStart + 1;
int numJumps = (rowLen - 1) / MAX_BYTE;
int colOffset = rowEnd - nullMaskSize - numJumps - 1;
-
+
// If last jump is a dummy value, ignore it
if(((colOffset - rowStart - numVarCols) / MAX_BYTE) < numJumps) {
numJumps--;
@@ -840,17 +847,17 @@ public class TableImpl implements Table
int jumpsUsed = 0;
for(int i = 0; i < numVarCols + 1; i++) {
- while((jumpsUsed < numJumps) &&
+ while((jumpsUsed < numJumps) &&
(i == ByteUtil.getUnsignedByte(
rowBuffer, rowEnd - nullMaskSize-jumpsUsed - 1))) {
jumpsUsed++;
}
-
+
varColOffsets[i] = (short)
(ByteUtil.getUnsignedByte(rowBuffer, colOffset - i)
+ (jumpsUsed * MAX_BYTE));
}
-
+
rowState.setVarColOffsets(varColOffsets);
return varColOffsets;
}
@@ -867,7 +874,7 @@ public class TableImpl implements Table
// Number of columns in this row
int columnCount = ByteUtil.getUnsignedVarInt(
rowBuffer, getFormat().SIZE_ROW_COLUMN_COUNT);
-
+
// read null mask
NullMask nullMask = new NullMask(columnCount);
rowBuffer.position(rowBuffer.limit() - nullMask.byteSize()); //Null mask at end
@@ -880,11 +887,11 @@ public class TableImpl implements Table
* Sets a new buffer to the correct row header page using the given rowState
* according to the given rowId. Deleted state is
* determined, but overflow row pointers are not followed.
- *
+ *
* @return a ByteBuffer of the relevant page, or null if row was invalid
* @usage _advanced_method_
*/
- public static ByteBuffer positionAtRowHeader(RowState rowState,
+ public static ByteBuffer positionAtRowHeader(RowState rowState,
RowIdImpl rowId)
throws IOException
{
@@ -894,7 +901,7 @@ public class TableImpl implements Table
// this task has already been accomplished
return rowBuffer;
}
-
+
if(!rowState.isValid()) {
// this was an invalid page/row
rowState.setStatus(RowStateStatus.AT_HEADER);
@@ -919,17 +926,17 @@ public class TableImpl implements Table
rowState.setStatus(RowStateStatus.AT_HEADER);
return rowBuffer;
}
-
+
/**
* Sets the position and limit in a new buffer using the given rowState
* according to the given row number and row end, following overflow row
* pointers as necessary.
- *
+ *
* @return a ByteBuffer narrowed to the actual row data, or null if row was
* invalid or deleted
* @usage _advanced_method_
*/
- public static ByteBuffer positionAtRowData(RowState rowState,
+ public static ByteBuffer positionAtRowData(RowState rowState,
RowIdImpl rowId)
throws IOException
{
@@ -943,7 +950,7 @@ public class TableImpl implements Table
ByteBuffer rowBuffer = rowState.getFinalPage();
int rowNum = rowState.getFinalRowId().getRowNumber();
JetFormat format = rowState.getTable().getFormat();
-
+
if(rowState.isAtFinalRow()) {
// we've already found the final row data
return PageChannel.narrowBuffer(
@@ -951,9 +958,9 @@ public class TableImpl implements Table
findRowStart(rowBuffer, rowNum, format),
findRowEnd(rowBuffer, rowNum, format));
}
-
+
while(true) {
-
+
// note, we don't use findRowStart here cause we need the unmasked value
short rowStart = rowBuffer.getShort(getRowStartOffset(rowNum, format));
short rowEnd = findRowEnd(rowBuffer, rowNum, format);
@@ -972,7 +979,7 @@ public class TableImpl implements Table
throw new IOException(rowState.getTable().withErrorContext(
"invalid overflow row info"));
}
-
+
// Overflow page. the "row" data in the current page points to
// another page/row
int overflowRowNum = ByteUtil.getUnsignedByte(rowBuffer, rowStart);
@@ -980,7 +987,7 @@ public class TableImpl implements Table
rowBuffer = rowState.setOverflowRow(
new RowIdImpl(overflowPageNum, overflowRowNum));
rowNum = overflowRowNum;
-
+
} else {
rowState.setStatus(RowStateStatus.AT_FINAL);
@@ -1006,13 +1013,13 @@ public class TableImpl implements Table
// next, determine how big the table def will be (in case it will be more
// than one page)
JetFormat format = creator.getFormat();
- int idxDataLen = (creator.getIndexCount() *
- (format.SIZE_INDEX_DEFINITION +
- format.SIZE_INDEX_COLUMN_BLOCK)) +
+ int idxDataLen = (creator.getIndexCount() *
+ (format.SIZE_INDEX_DEFINITION +
+ format.SIZE_INDEX_COLUMN_BLOCK)) +
(creator.getLogicalIndexCount() * format.SIZE_INDEX_INFO_BLOCK);
int colUmapLen = creator.getLongValueColumns().size() * 10;
int totalTableDefSize = format.SIZE_TDEF_HEADER +
- (format.SIZE_COLUMN_DEF_BLOCK * creator.getColumns().size()) +
+ (format.SIZE_COLUMN_DEF_BLOCK * creator.getColumns().size()) +
idxDataLen + colUmapLen + format.SIZE_TDEF_TRAILER;
// total up the amount of space used by the column and index names (2
@@ -1020,11 +1027,11 @@ public class TableImpl implements Table
for(ColumnBuilder col : creator.getColumns()) {
totalTableDefSize += DBMutator.calculateNameLength(col.getName());
}
-
+
for(IndexBuilder idx : creator.getIndexes()) {
totalTableDefSize += DBMutator.calculateNameLength(idx.getName());
}
-
+
// now, create the table definition
ByteBuffer buffer = PageChannel.createBuffer(Math.max(totalTableDefSize,
@@ -1037,8 +1044,8 @@ public class TableImpl implements Table
}
// column definitions
- ColumnImpl.writeDefinitions(creator, buffer);
-
+ ColumnImpl.writeDefinitions(creator, buffer);
+
if(creator.hasIndexes()) {
// index and index data definitions
IndexData.writeDefinitions(creator, buffer);
@@ -1059,7 +1066,7 @@ public class TableImpl implements Table
}
private static void writeTableDefinitionBuffer(
- ByteBuffer buffer, int tdefPageNumber,
+ ByteBuffer buffer, int tdefPageNumber,
TableMutator mutator, List<Integer> reservedPages)
throws IOException
{
@@ -1070,7 +1077,7 @@ public class TableImpl implements Table
// write table buffer to database
if(totalTableDefSize <= format.PAGE_SIZE) {
-
+
// easy case, fits on one page
// overwrite page free space
@@ -1080,7 +1087,7 @@ public class TableImpl implements Table
// Write the tdef page to disk.
buffer.clear();
pageChannel.writePage(buffer, tdefPageNumber);
-
+
} else {
// need to split across multiple pages
@@ -1092,13 +1099,13 @@ public class TableImpl implements Table
// reset for next write
partialTdef.clear();
-
+
if(nextTdefPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
-
+
// this is the first page. note, the first page already has the
// page header, so no need to write it here
nextTdefPageNumber = tdefPageNumber;
-
+
} else {
// write page header
@@ -1130,9 +1137,9 @@ public class TableImpl implements Table
// write partial page to disk
pageChannel.writePage(partialTdef, curTdefPageNumber);
}
-
+
}
-
+
}
/**
@@ -1166,7 +1173,7 @@ public class TableImpl implements Table
int umapPos = -1;
boolean success = false;
try {
-
+
////
// update various bits of the table def
ByteUtil.forward(tableBuffer, 29);
@@ -1176,7 +1183,7 @@ public class TableImpl implements Table
tableBuffer.putShort((short)(_columns.size() + 1));
// move to end of column def blocks
- tableBuffer.position(format.SIZE_TDEF_HEADER +
+ tableBuffer.position(format.SIZE_TDEF_HEADER +
(_indexCount * format.SIZE_INDEX_DEFINITION) +
(_columns.size() * format.SIZE_COLUMN_DEF_BLOCK));
@@ -1193,9 +1200,9 @@ public class TableImpl implements Table
} else {
// find the fixed offset
for(ColumnImpl col : _columns) {
- if(!col.isVariableLength() &&
+ if(!col.isVariableLength() &&
(col.getFixedDataOffset() >= fixedOffset)) {
- fixedOffset = col.getFixedDataOffset() +
+ fixedOffset = col.getFixedDataOffset() +
col.getType().getFixedSize(col.getLength());
}
}
@@ -1224,7 +1231,7 @@ public class TableImpl implements Table
colState.setUmapFreeRowNumber((byte)(rowNum + 1));
// skip past index defs
- ByteUtil.forward(tableBuffer, (_indexCount *
+ ByteUtil.forward(tableBuffer, (_indexCount *
format.SIZE_INDEX_COLUMN_BLOCK));
ByteUtil.forward(tableBuffer,
(_logicalIndexCount * format.SIZE_INDEX_INFO_BLOCK));
@@ -1237,7 +1244,7 @@ public class TableImpl implements Table
ByteUtil.forward(tableBuffer, -2);
break;
}
-
+
ByteUtil.forward(tableBuffer, 8);
// keep reading ...
@@ -1260,7 +1267,7 @@ public class TableImpl implements Table
////
// write updated table def back to the database
- writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
+ writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
mutator.getNextPages());
success = true;
@@ -1295,7 +1302,7 @@ public class TableImpl implements Table
if(!isSystem()) {
// after fully constructed, allow column validator to be configured (but
// only for user tables)
- newCol.setColumnValidator(null);
+ newCol.initColumnValidator();
}
// save any column properties
@@ -1321,7 +1328,7 @@ public class TableImpl implements Table
////
// calculate how much more space we need in the table def
- mutator.addTdefLen(format.SIZE_INDEX_DEFINITION +
+ mutator.addTdefLen(format.SIZE_INDEX_DEFINITION +
format.SIZE_INDEX_COLUMN_BLOCK);
////
@@ -1332,14 +1339,14 @@ public class TableImpl implements Table
IndexData newIdxData = null;
boolean success = false;
try {
-
+
////
// update various bits of the table def
ByteUtil.forward(tableBuffer, 39);
tableBuffer.putInt(_indexCount + 1);
// move to end of index data def blocks
- tableBuffer.position(format.SIZE_TDEF_HEADER +
+ tableBuffer.position(format.SIZE_TDEF_HEADER +
(_indexCount * format.SIZE_INDEX_DEFINITION));
// write index row count definition (empty initially)
@@ -1347,12 +1354,12 @@ public class TableImpl implements Table
IndexData.writeRowCountDefinitions(mutator, tableBuffer, 1);
// skip columns and column names
- ByteUtil.forward(tableBuffer,
+ ByteUtil.forward(tableBuffer,
(_columns.size() * format.SIZE_COLUMN_DEF_BLOCK));
skipNames(tableBuffer, _columns.size());
// move to end of current index datas
- ByteUtil.forward(tableBuffer, (_indexCount *
+ ByteUtil.forward(tableBuffer, (_indexCount *
format.SIZE_INDEX_COLUMN_BLOCK));
// allocate usage maps and root page
@@ -1380,7 +1387,7 @@ public class TableImpl implements Table
////
// write updated table def back to the database
- writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
+ writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
mutator.getNextPages());
success = true;
@@ -1397,7 +1404,7 @@ public class TableImpl implements Table
for(IndexData.ColumnDescriptor iCol : newIdxData.getColumns()) {
_indexColumns.add(iCol.getColumn());
}
-
+
++_indexCount;
_indexDatas.add(newIdxData);
@@ -1425,7 +1432,7 @@ public class TableImpl implements Table
col.setRowValue(rowVals, col.getRowValue(row));
}
- IndexData.commitAll(
+ IndexData.commitAll(
idxData.prepareAddRow(rowVals, (RowIdImpl)row.getId(), null));
}
@@ -1456,26 +1463,26 @@ public class TableImpl implements Table
IndexImpl newIdx = null;
boolean success = false;
try {
-
+
////
// update various bits of the table def
ByteUtil.forward(tableBuffer, 35);
tableBuffer.putInt(_logicalIndexCount + 1);
// move to end of index data def blocks
- tableBuffer.position(format.SIZE_TDEF_HEADER +
+ tableBuffer.position(format.SIZE_TDEF_HEADER +
(_indexCount * format.SIZE_INDEX_DEFINITION));
// skip columns and column names
- ByteUtil.forward(tableBuffer,
+ ByteUtil.forward(tableBuffer,
(_columns.size() * format.SIZE_COLUMN_DEF_BLOCK));
skipNames(tableBuffer, _columns.size());
// move to end of current index datas
- ByteUtil.forward(tableBuffer, (_indexCount *
+ ByteUtil.forward(tableBuffer, (_indexCount *
format.SIZE_INDEX_COLUMN_BLOCK));
// move to end of current indexes
- ByteUtil.forward(tableBuffer, (_logicalIndexCount *
+ ByteUtil.forward(tableBuffer, (_logicalIndexCount *
format.SIZE_INDEX_INFO_BLOCK));
int idxDefPos = tableBuffer.position();
@@ -1494,10 +1501,10 @@ public class TableImpl implements Table
tableBuffer.position(idxDefPos);
newIdx = new IndexImpl(tableBuffer, _indexDatas, format);
newIdx.setName(index.getName());
-
+
////
// write updated table def back to the database
- writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
+ writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
mutator.getNextPages());
success = true;
@@ -1547,7 +1554,7 @@ public class TableImpl implements Table
private static void skipNames(ByteBuffer tableBuffer, int count) {
for(int i = 0; i < count; ++i) {
ByteUtil.forward(tableBuffer, tableBuffer.getShort());
- }
+ }
}
private ByteBuffer loadCompleteTableDefinitionBufferForUpdate(
@@ -1616,7 +1623,7 @@ public class TableImpl implements Table
}
if(umapPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
-
+
// didn't find any existing pages, need to create a new one
umapPageNumber = pageChannel.allocateNewPage();
freeSpace = format.DATA_PAGE_INITIAL_FREE_SPACE;
@@ -1637,14 +1644,14 @@ public class TableImpl implements Table
umapBuf.put(rowStart + 5, (byte)1);
}
- rowStart -= umapRowLength;
+ rowStart -= umapRowLength;
++umapRowNum;
}
// finish the page
freeSpace -= totalUmapSpaceUsage;
umapBuf.putShort(format.OFFSET_FREE_SPACE, (short)freeSpace);
- umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
+ umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
(short)umapRowNum);
pageChannel.writePage(umapBuf, umapPageNumber);
@@ -1663,8 +1670,8 @@ public class TableImpl implements Table
for(ColumnImpl col : _columns) {
col.collectUsageMapPages(pages);
}
- }
-
+ }
+
/**
* @param buffer Buffer to write to
*/
@@ -1708,7 +1715,7 @@ public class TableImpl implements Table
buffer.put((byte) 0); //Unknown
buffer.putInt(0); //Next TDEF page pointer
}
-
+
/**
* Writes the given name into the given buffer in the format as expected by
* {@link #readName}.
@@ -1719,7 +1726,7 @@ public class TableImpl implements Table
buffer.putShort((short) encName.remaining());
buffer.put(encName);
}
-
+
/**
* Create the usage map definition page buffer. The "used pages" map is in
* row 0, the "pages with free space" map is in row 1. Index usage maps are
@@ -1744,7 +1751,7 @@ public class TableImpl implements Table
int freeSpace = 0;
int rowStart = 0;
int umapRowNum = 0;
-
+
for(int i = 0; i < umapNum; ++i) {
if(umapBuf == null) {
@@ -1767,7 +1774,7 @@ public class TableImpl implements Table
}
umapBuf.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart);
-
+
if(i == 0) {
// table "owned pages" map definition
@@ -1782,9 +1789,9 @@ public class TableImpl implements Table
// index umap
int indexIdx = i - 2;
- TableMutator.IndexDataState idxDataState =
+ TableMutator.IndexDataState idxDataState =
creator.getIndexDataStates().get(indexIdx);
-
+
// allocate root page for the index
int rootPageNumber = pageChannel.allocateNewPage();
@@ -1806,19 +1813,19 @@ public class TableImpl implements Table
lvalColIdx /= 2;
ColumnBuilder lvalCol = lvalCols.get(lvalColIdx);
- TableMutator.ColumnState colState =
+ TableMutator.ColumnState colState =
creator.getColumnState(lvalCol);
umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
- if((umapType == 1) &&
+ if((umapType == 1) &&
(umapPageNumber != colState.getUmapPageNumber())) {
// we want to force both usage maps for a column to be on the same
// data page, so just discard the previous one we wrote
--i;
umapType = 0;
}
-
+
if(umapType == 0) {
// lval column "owned pages" usage map
colState.setUmapOwnedRowNumber((byte)umapRowNum);
@@ -1836,7 +1843,7 @@ public class TableImpl implements Table
if((freeSpace <= umapSpaceUsage) || (i == (umapNum - 1))) {
// finish current page
umapBuf.putShort(format.OFFSET_FREE_SPACE, (short)freeSpace);
- umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
+ umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
(short)umapRowNum);
pageChannel.writePage(umapBuf, umapPageNumber);
umapBuf = null;
@@ -1853,7 +1860,7 @@ public class TableImpl implements Table
umapBuf.putShort((short)freeSpace); //Free space in page
umapBuf.putInt(0); //Table definition
umapBuf.putInt(0); //Unknown
- umapBuf.putShort((short)0); //Number of records on this page
+ umapBuf.putShort((short)0); //Number of records on this page
return umapBuf;
}
@@ -1882,14 +1889,14 @@ public class TableImpl implements Table
}
return tableBuffer;
}
-
+
private ByteBuffer expandTableBuffer(ByteBuffer tableBuffer) {
ByteBuffer newBuffer = PageChannel.createBuffer(
tableBuffer.capacity() + getFormat().PAGE_SIZE - 8);
newBuffer.put(tableBuffer);
return newBuffer;
}
-
+
private void readColumnDefinitions(ByteBuffer tableBuffer, short columnCount)
throws IOException
{
@@ -1901,8 +1908,8 @@ public class TableImpl implements Table
List<String> colNames = new ArrayList<String>(columnCount);
for (int i = 0; i < columnCount; i++) {
colNames.add(readName(tableBuffer));
- }
-
+ }
+
int dispIndex = 0;
for (int i = 0; i < columnCount; i++) {
ColumnImpl column = ColumnImpl.create(this, tableBuffer,
@@ -1951,18 +1958,18 @@ public class TableImpl implements Table
for (int i = 0; i < _logicalIndexCount; i++) {
_indexes.get(i).setName(readName(tableBuffer));
}
-
+
Collections.sort(_indexes);
}
-
- private boolean readColumnUsageMaps(ByteBuffer tableBuffer)
+
+ private boolean readColumnUsageMaps(ByteBuffer tableBuffer)
throws IOException
{
short umapColNum = tableBuffer.getShort();
if(umapColNum == IndexData.COLUMN_UNUSED) {
return false;
}
-
+
int pos = tableBuffer.position();
UsageMap colOwnedPages = null;
UsageMap colFreeSpacePages = null;
@@ -1974,10 +1981,10 @@ public class TableImpl implements Table
colOwnedPages = null;
colFreeSpacePages = null;
tableBuffer.position(pos + 8);
- LOG.warn(withErrorContext("Invalid column " + umapColNum +
+ LOG.warn(withErrorContext("Invalid column " + umapColNum +
" usage map definition: " + e));
}
-
+
for(ColumnImpl col : _columns) {
if(col.getColumnNumber() == umapColNum) {
col.setUsageMaps(colOwnedPages, colFreeSpacePages);
@@ -2001,7 +2008,7 @@ public class TableImpl implements Table
// possibly invalidate the add row buffer if a different data buffer is
// being written (e.g. this happens during deleteRow)
_addRowBufferH.possiblyInvalidate(pageNumber, pageBuffer);
-
+
// update modification count so any active RowStates can keep themselves
// up-to-date
++_modCount;
@@ -2009,27 +2016,27 @@ public class TableImpl implements Table
/**
* Returns a name read from the buffer at the current position. The
- * expected name format is the name length followed by the name
+ * expected name format is the name length followed by the name
* encoded using the {@link JetFormat#CHARSET}
*/
- private String readName(ByteBuffer buffer) {
+ private String readName(ByteBuffer buffer) {
int nameLength = readNameLength(buffer);
byte[] nameBytes = ByteUtil.getBytes(buffer, nameLength);
- return ColumnImpl.decodeUncompressedText(nameBytes,
+ return ColumnImpl.decodeUncompressedText(nameBytes,
getDatabase().getCharset());
}
-
+
/**
* Returns a name length read from the buffer at the current position.
*/
- private int readNameLength(ByteBuffer buffer) {
+ private int readNameLength(ByteBuffer buffer) {
return ByteUtil.getUnsignedVarInt(buffer, getFormat().SIZE_NAME_LENGTH);
}
-
+
public Object[] asRow(Map<String,?> rowMap) {
return asRow(rowMap, null, false);
}
-
+
/**
* Converts a map of columnName -> columnValue to an array of row values
* appropriate for a call to {@link #addRow(Object...)}, where the generated
@@ -2040,7 +2047,7 @@ public class TableImpl implements Table
public Object[] asRowWithRowId(Map<String,?> rowMap) {
return asRow(rowMap, null, true);
}
-
+
public Object[] asUpdateRow(Map<String,?> rowMap) {
return asRow(rowMap, Column.KEEP_VALUE, false);
}
@@ -2057,7 +2064,7 @@ public class TableImpl implements Table
/**
* Converts a map of columnName -> columnValue to an array of row values.
*/
- private Object[] asRow(Map<String,?> rowMap, Object defaultValue,
+ private Object[] asRow(Map<String,?> rowMap, Object defaultValue,
boolean returnRowId)
{
int len = _columns.size();
@@ -2081,13 +2088,13 @@ public class TableImpl implements Table
}
return row;
}
-
+
public Object[] addRow(Object... row) throws IOException {
return addRows(Collections.singletonList(row), false).get(0);
}
- public <M extends Map<String,Object>> M addRowFromMap(M row)
- throws IOException
+ public <M extends Map<String,Object>> M addRowFromMap(M row)
+ throws IOException
{
Object[] rowValues = asRow(row);
@@ -2096,20 +2103,20 @@ public class TableImpl implements Table
returnRowValues(row, rowValues, _autoNumColumns);
return row;
}
-
- public List<? extends Object[]> addRows(List<? extends Object[]> rows)
- throws IOException
+
+ public List<? extends Object[]> addRows(List<? extends Object[]> rows)
+ throws IOException
{
return addRows(rows, true);
}
-
- public <M extends Map<String,Object>> List<M> addRowsFromMaps(List<M> rows)
- throws IOException
+
+ public <M extends Map<String,Object>> List<M> addRowsFromMaps(List<M> rows)
+ throws IOException
{
List<Object[]> rowValuesList = new ArrayList<Object[]>(rows.size());
for(Map<String,Object> row : rows) {
rowValuesList.add(asRow(row));
- }
+ }
addRows(rowValuesList);
@@ -2146,12 +2153,12 @@ public class TableImpl implements Table
getPageChannel().startWrite();
try {
-
+
ByteBuffer dataPage = null;
int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
int updateCount = 0;
int autoNumAssignCount = 0;
- WriteRowState writeRowState =
+ WriteRowState writeRowState =
(!_autoNumColumns.isEmpty() ? new WriteRowState() : null);
try {
@@ -2179,7 +2186,7 @@ public class TableImpl implements Table
// handle various value massaging activities
for(ColumnImpl column : _columns) {
- if(!column.isAutoNumber()) {
+ if(!column.isAutoNumber()) {
// pass input value through column validator
column.setRowValue(row, column.validate(column.getRowValue(row)));
}
@@ -2188,14 +2195,14 @@ public class TableImpl implements Table
// fill in autonumbers
handleAutoNumbersForAdd(row, writeRowState);
++autoNumAssignCount;
-
+
// write the row of data to a temporary buffer
ByteBuffer rowData = createRow(
row, _writeRowBufferH.getPageBuffer(getPageChannel()));
-
+
int rowSize = rowData.remaining();
if (rowSize > getFormat().MAX_ROW_SIZE) {
- throw new IOException(withErrorContext(
+ throw new InvalidValueException(withErrorContext(
"Row size " + rowSize + " is too large"));
}
@@ -2237,7 +2244,7 @@ public class TableImpl implements Table
dataPage.put(rowData);
// return rowTd if desired
- if((row.length > numCols) &&
+ if((row.length > numCols) &&
(row[numCols] == ColumnImpl.RETURN_ROW_ID)) {
row[numCols] = rowId;
}
@@ -2246,7 +2253,7 @@ public class TableImpl implements Table
}
writeDataPage(dataPage, pageNumber);
-
+
// Update tdef page
updateTableDefinition(rows.size());
@@ -2259,12 +2266,12 @@ public class TableImpl implements Table
// recover them so we don't get ugly "holes"
restoreAutoNumbersFromAdd(rows.get(autoNumAssignCount - 1));
}
-
+
if(!isBatchWrite) {
// just re-throw the original exception
if(rowWriteFailure instanceof IOException) {
throw (IOException)rowWriteFailure;
- }
+ }
throw (RuntimeException)rowWriteFailure;
}
@@ -2276,12 +2283,12 @@ public class TableImpl implements Table
updateCount = 0;
} else if(updateCount > 0) {
-
+
// attempt to flush the rows already written to disk
try {
writeDataPage(dataPage, pageNumber);
-
+
// Update tdef page
updateTableDefinition(updateCount);
@@ -2291,7 +2298,7 @@ public class TableImpl implements Table
// write failure). we don't know the status of any rows at this
// point (and the original failure is probably irrelevant)
LOG.warn(withErrorContext(
- "Secondary row failure which preceded the write failure"),
+ "Secondary row failure which preceded the write failure"),
rowWriteFailure);
updateCount = 0;
rowWriteFailure = flushFailure;
@@ -2306,7 +2313,7 @@ public class TableImpl implements Table
} finally {
getPageChannel().finishWrite();
}
-
+
return rows;
}
@@ -2316,11 +2323,11 @@ public class TableImpl implements Table
return true;
}
t = t.getCause();
- }
+ }
// some other sort of exception which is not a write failure
return false;
}
-
+
public Row updateRow(Row row) throws IOException {
return updateRowFromMap(
getDefaultCursor().getRowState(), (RowIdImpl)row.getId(), row);
@@ -2344,8 +2351,8 @@ public class TableImpl implements Table
* @throws IllegalStateException if the given row is not valid, or deleted.
* @usage _intermediate_method_
*/
- public void updateValue(Column column, RowId rowId, Object value)
- throws IOException
+ public void updateValue(Column column, RowId rowId, Object value)
+ throws IOException
{
Object[] row = new Object[_columns.size()];
Arrays.fill(row, Column.KEEP_VALUE);
@@ -2355,8 +2362,8 @@ public class TableImpl implements Table
}
public <M extends Map<String,Object>> M updateRowFromMap(
- RowState rowState, RowIdImpl rowId, M row)
- throws IOException
+ RowState rowState, RowIdImpl rowId, M row)
+ throws IOException
{
Object[] rowValues = updateRow(rowState, rowId, asUpdateRow(row));
returnRowValues(row, rowValues, _columns);
@@ -2367,14 +2374,14 @@ public class TableImpl implements Table
* Update the row for the given rowId.
* @usage _advanced_method_
*/
- public Object[] updateRow(RowState rowState, RowIdImpl rowId, Object... row)
- throws IOException
+ public Object[] updateRow(RowState rowState, RowIdImpl rowId, Object... row)
+ throws IOException
{
requireValidRowId(rowId);
-
+
getPageChannel().startWrite();
try {
-
+
// ensure that the relevant row state is up-to-date
ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
int oldRowSize = rowBuffer.remaining();
@@ -2390,7 +2397,7 @@ public class TableImpl implements 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<ColumnImpl,byte[]> keepRawVarValues =
+ Map<ColumnImpl,byte[]> keepRawVarValues =
(!_varColumns.isEmpty() ? new HashMap<ColumnImpl,byte[]>() : null);
// handle various value massaging activities
@@ -2403,11 +2410,11 @@ public class TableImpl implements Table
Object rowValue = column.getRowValue(row);
if(rowValue == Column.KEEP_VALUE) {
-
+
// fill in any "keep value" fields (restore old value)
rowValue = getRowColumn(getFormat(), rowBuffer, column, rowState,
keepRawVarValues);
-
+
} else {
// set oldValue to something that could not possibly be a real value
@@ -2424,7 +2431,7 @@ public class TableImpl implements Table
if(oldValue != rowValue) {
// pass input value through column validator
rowValue = column.validate(rowValue);
- }
+ }
}
column.setRowValue(row, rowValue);
@@ -2439,7 +2446,7 @@ public class TableImpl implements Table
keepRawVarValues);
if (newRowData.limit() > getFormat().MAX_ROW_SIZE) {
- throw new IOException(withErrorContext(
+ throw new InvalidValueException(withErrorContext(
"Row size " + newRowData.limit() + " is too large"));
}
@@ -2455,7 +2462,7 @@ public class TableImpl implements Table
// prepare index updates
for(IndexData indexData : _indexDatas) {
- idxChange = indexData.prepareUpdateRow(oldRowValues, rowId, row,
+ idxChange = indexData.prepareUpdateRow(oldRowValues, rowId, row,
idxChange);
}
@@ -2467,7 +2474,7 @@ public class TableImpl implements Table
throw ce;
}
}
-
+
// see if we can squeeze the new row data into the existing row
rowBuffer.reset();
int rowSize = newRowData.remaining();
@@ -2487,11 +2494,11 @@ public class TableImpl implements Table
} else {
// bummer, need to find a new page for the data
- dataPage = findFreeRowSpace(rowSize, null,
+ dataPage = findFreeRowSpace(rowSize, null,
PageChannel.INVALID_PAGE_NUMBER);
pageNumber = _addRowBufferH.getPageNumber();
- RowIdImpl headerRowId = rowState.getHeaderRowId();
+ RowIdImpl headerRowId = rowState.getHeaderRowId();
ByteBuffer headerPage = rowState.getHeaderPage();
if(pageNumber == headerRowId.getPageNumber()) {
// new row is on the same page as header row, share page
@@ -2535,8 +2542,8 @@ public class TableImpl implements Table
return row;
}
-
- private ByteBuffer findFreeRowSpace(int rowSize, ByteBuffer dataPage,
+
+ private ByteBuffer findFreeRowSpace(int rowSize, ByteBuffer dataPage,
int pageNumber)
throws IOException
{
@@ -2546,7 +2553,7 @@ public class TableImpl implements Table
if(dataPage == null) {
// find owned page w/ free space
- dataPage = findFreeRowSpace(_ownedPages, _freeSpacePages,
+ dataPage = findFreeRowSpace(_ownedPages, _freeSpacePages,
_addRowBufferH);
if(dataPage == null) {
@@ -2602,7 +2609,7 @@ public class TableImpl implements Table
return null;
}
-
+
/**
* Updates the table definition after rows are modified.
*/
@@ -2611,7 +2618,7 @@ public class TableImpl implements Table
// load table definition
ByteBuffer tdefPage = _tableDefBufferH.setPage(getPageChannel(),
_tableDefPageNumber);
-
+
// make sure rowcount and autonumber are up-to-date
_rowCount += rowCountInc;
tdefPage.putInt(getFormat().OFFSET_NUM_ROWS, _rowCount);
@@ -2634,7 +2641,7 @@ public class TableImpl implements Table
// write modified table definition
getPageChannel().writePage(tdefPage, _tableDefPageNumber);
}
-
+
/**
* Create a new data page
* @return Page number of the new page
@@ -2653,7 +2660,7 @@ public class TableImpl implements Table
_freeSpacePages.addPageNumber(pageNumber);
return dataPage;
}
-
+
protected ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer)
throws IOException
{
@@ -2663,7 +2670,7 @@ public class TableImpl implements Table
/**
* Serialize a row of Objects into a byte buffer.
- *
+ *
* @param rowArray row data, expected to be correct length for this table
* @param buffer buffer to which to write the row data
* @param minRowSize min size for result row
@@ -2672,13 +2679,13 @@ public class TableImpl implements Table
* @return the given buffer, filled with the row data
*/
private ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer,
- int minRowSize,
+ int minRowSize,
Map<ColumnImpl,byte[]> rawVarValues)
throws IOException
{
buffer.putShort(_maxColumnCount);
NullMask nullMask = new NullMask(_maxColumnCount);
-
+
//Fixed length column data comes first
int fixedDataStart = buffer.position();
int fixedDataEnd = fixedDataStart;
@@ -2687,19 +2694,19 @@ public class TableImpl implements Table
if(col.isVariableLength()) {
continue;
}
-
+
Object rowValue = col.getRowValue(rowArray);
if (col.storeInNullMask()) {
-
+
if(col.writeToNullMask(rowValue)) {
nullMask.markNotNull(col);
}
rowValue = null;
}
-
+
if(rowValue != null) {
-
+
// we have a value to write
nullMask.markNotNull(col);
@@ -2717,13 +2724,13 @@ public class TableImpl implements Table
// keep track of the end of fixed data
if(buffer.position() > fixedDataEnd) {
fixedDataEnd = buffer.position();
- }
-
+ }
+
}
// reposition at end of fixed data
buffer.position(fixedDataEnd);
-
+
// only need this info if this table contains any var length data
if(_maxVarColumnCount > 0) {
@@ -2745,7 +2752,7 @@ public class TableImpl implements Table
maxRowSize -= getFormat().SIZE_LONG_VALUE_DEF;
}
}
-
+
//Now write out variable length column data
short[] varColumnOffsets = new short[_maxVarColumnCount];
int varColumnOffsetsIndex = 0;
@@ -2758,7 +2765,7 @@ public class TableImpl implements Table
byte[] rawValue = null;
ByteBuffer varDataBuf = null;
- if(((rawValue = rawVarValues.get(varCol)) != null) &&
+ if(((rawValue = rawVarValues.get(varCol)) != null) &&
(rawValue.length <= maxRowSize)) {
// save time and potentially db space, re-use raw value
varDataBuf = ByteBuffer.wrap(rawValue);
@@ -2778,9 +2785,9 @@ public class TableImpl implements Table
} catch(BufferOverflowException e) {
// if the data is too big for the buffer, then we have gone over
// the max row size
- throw new IOException(withErrorContext(
+ throw new InvalidValueException(withErrorContext(
"Row size " + buffer.limit() + " is too large"));
- }
+ }
}
// we do a loop here so that we fill in offsets for deleted columns
@@ -2839,14 +2846,14 @@ public class TableImpl implements Table
Object inRowValue = getInputAutoNumberRowValue(enableInsert, col, row);
ColumnImpl.AutoNumberGenerator autoNumGen = col.getAutoNumberGenerator();
- Object rowValue = ((inRowValue == null) ?
+ Object rowValue = ((inRowValue == null) ?
autoNumGen.getNext(writeRowState) :
autoNumGen.handleInsert(writeRowState, inRowValue));
col.setRowValue(row, rowValue);
}
}
-
+
/**
* Fill in all autonumber column values for update.
*/
@@ -2866,7 +2873,7 @@ public class TableImpl implements Table
// enabled)
Object inRowValue = getInputAutoNumberRowValue(enableInsert, col, row);
- Object rowValue =
+ Object rowValue =
((inRowValue == null) ?
getRowColumn(getFormat(), rowBuffer, col, rowState, null) :
col.getAutoNumberGenerator().handleInsert(rowState, inRowValue));
@@ -2893,7 +2900,7 @@ public class TableImpl implements Table
}
return inRowValue;
}
-
+
/**
* Restores all autonumber column values from a failed add row.
*/
@@ -2946,7 +2953,7 @@ public class TableImpl implements Table
// restores the last used auto number
_lastLongAutoNumber = lastLongAutoNumber - 1;
}
-
+
int getNextComplexTypeAutoNumber() {
// note, the saved value is the last one handed out, so pre-increment
return ++_lastComplexTypeAutoNumber;
@@ -2967,7 +2974,7 @@ public class TableImpl implements Table
// restores the last used auto number
_lastComplexTypeAutoNumber = lastComplexTypeAutoNumber - 1;
}
-
+
@Override
public String toString() {
return CustomToStringStyle.builder(this)
@@ -2982,7 +2989,7 @@ public class TableImpl implements Table
.append("ownedPages", _ownedPages)
.toString();
}
-
+
/**
* @return A simple String representation of the entire table in
* tab-delimited format
@@ -2991,7 +2998,7 @@ public class TableImpl implements Table
public String display() throws IOException {
return display(Long.MAX_VALUE);
}
-
+
/**
* @param limit Maximum number of rows to display
* @return A simple String representation of the entire table in
@@ -3014,11 +3021,11 @@ public class TableImpl implements Table
*/
public static int addDataPageRow(ByteBuffer dataPage,
int rowSize,
- JetFormat format,
+ JetFormat format,
int rowFlags)
{
int rowSpaceUsage = getRowSpaceUsage(rowSize, format);
-
+
// Decrease free space record.
short freeSpaceInPage = dataPage.getShort(format.OFFSET_FREE_SPACE);
dataPage.putShort(format.OFFSET_FREE_SPACE, (short) (freeSpaceInPage -
@@ -3034,7 +3041,7 @@ public class TableImpl implements Table
rowLocation -= rowSize;
// write row position
- dataPage.putShort(getRowStartOffset(rowCount, format),
+ dataPage.putShort(getRowStartOffset(rowCount, format),
(short)(rowLocation | rowFlags));
// set position for row data
@@ -3066,7 +3073,7 @@ public class TableImpl implements Table
"Given rowId is invalid: " + rowId));
}
}
-
+
/**
* @throws IllegalStateException if the given row is invalid or deleted
*/
@@ -3081,14 +3088,14 @@ public class TableImpl implements Table
"Row is deleted: " + rowId));
}
}
-
+
/**
* @usage _advanced_method_
*/
public static boolean isDeletedRow(short rowStart) {
return ((rowStart & DELETED_ROW_MASK) != 0);
}
-
+
/**
* @usage _advanced_method_
*/
@@ -3102,7 +3109,7 @@ public class TableImpl implements Table
public static short cleanRowStart(short rowStart) {
return (short)(rowStart & OFFSET_MASK);
}
-
+
/**
* @usage _advanced_method_
*/
@@ -3120,7 +3127,7 @@ public class TableImpl implements Table
{
return format.OFFSET_ROW_START + (format.SIZE_ROW_LOCATION * rowNum);
}
-
+
/**
* @usage _advanced_method_
*/
@@ -3262,7 +3269,7 @@ public class TableImpl implements Table
private ErrorHandler _errorHandler;
/** cached variable column offsets for jump-table based rows */
private short[] _varColOffsets;
-
+
private RowState(TempBufferHolder.Type headerType) {
_headerRowBufferH = TempPageHolder.newHolder(headerType);
_rowValues = new Object[TableImpl.this.getColumnCount()];
@@ -3281,7 +3288,7 @@ public class TableImpl implements Table
public void setErrorHandler(ErrorHandler newErrorHandler) {
_errorHandler = newErrorHandler;
}
-
+
public void reset() {
resetAutoNumber();
_finalRowId = null;
@@ -3300,7 +3307,7 @@ public class TableImpl implements Table
public boolean isUpToDate() {
return(TableImpl.this._modCount == _lastModCount);
}
-
+
private void checkForModification() {
if(!isUpToDate()) {
reset();
@@ -3314,7 +3321,7 @@ public class TableImpl implements Table
_lastModCount = TableImpl.this._modCount;
}
}
-
+
private ByteBuffer getFinalPage()
throws IOException
{
@@ -3339,11 +3346,11 @@ public class TableImpl implements Table
public boolean isValid() {
return(_rowStatus.ordinal() >= RowStatus.VALID.ordinal());
}
-
+
public boolean isDeleted() {
return(_rowStatus == RowStatus.DELETED);
}
-
+
public boolean isOverflow() {
return(_rowStatus == RowStatus.OVERFLOW);
}
@@ -3351,19 +3358,19 @@ public class TableImpl implements Table
public boolean isHeaderPageNumberValid() {
return(_rowStatus.ordinal() > RowStatus.INVALID_PAGE.ordinal());
}
-
+
public boolean isHeaderRowNumberValid() {
return(_rowStatus.ordinal() > RowStatus.INVALID_ROW.ordinal());
}
-
+
private void setStatus(RowStateStatus status) {
_status = status;
}
-
+
public boolean isAtHeaderRow() {
return(_status.ordinal() >= RowStateStatus.AT_HEADER.ordinal());
}
-
+
public boolean isAtFinalRow() {
return(_status.ordinal() >= RowStateStatus.AT_FINAL.ordinal());
}
@@ -3380,7 +3387,7 @@ public class TableImpl implements Table
// modified externally and therefore could return an incorrect value
return(ColumnImpl.isImmutableValue(value) ? value : null);
}
-
+
public Object[] getRowCacheValues() {
return dupeRow(_rowValues, _rowValues.length);
}
@@ -3399,7 +3406,7 @@ public class TableImpl implements Table
private void setVarColOffsets(short[] varColOffsets) {
_varColOffsets = varColOffsets;
}
-
+
public RowIdImpl getHeaderRowId() {
return _headerRowId;
}
@@ -3407,7 +3414,7 @@ public class TableImpl implements Table
public int getRowsOnHeaderPage() {
return _rowsOnHeaderPage;
}
-
+
private ByteBuffer getHeaderPage()
throws IOException
{
@@ -3436,11 +3443,11 @@ public class TableImpl implements Table
setRowStatus(RowStatus.INVALID_PAGE);
return null;
}
-
+
_finalRowBuffer = _headerRowBufferH.setPage(getPageChannel(),
pageNumber);
_rowsOnHeaderPage = getRowsOnDataPage(_finalRowBuffer, getFormat());
-
+
if((rowNumber < 0) || (rowNumber >= _rowsOnHeaderPage)) {
setRowStatus(RowStatus.INVALID_ROW);
return null;
@@ -3475,7 +3482,7 @@ public class TableImpl implements Table
{
return getErrorHandler().handleRowError(column, columnData,
this, error);
- }
+ }
@Override
public String toString() {
@@ -3485,5 +3492,5 @@ public class TableImpl implements Table
.toString();
}
}
-
+
}
diff --git a/src/test/java/com/healthmarketscience/jackcess/PropertiesTest.java b/src/test/java/com/healthmarketscience/jackcess/PropertiesTest.java
index 69eb7c6..08cedc0 100644
--- a/src/test/java/com/healthmarketscience/jackcess/PropertiesTest.java
+++ b/src/test/java/com/healthmarketscience/jackcess/PropertiesTest.java
@@ -21,9 +21,11 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
import static com.healthmarketscience.jackcess.Database.*;
+import com.healthmarketscience.jackcess.InvalidValueException;
import com.healthmarketscience.jackcess.impl.DatabaseImpl;
import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
import com.healthmarketscience.jackcess.impl.PropertyMapImpl;
@@ -44,7 +46,7 @@ public class PropertiesTest extends TestCase
public void testPropertyMaps() throws Exception
{
- PropertyMaps maps = new PropertyMaps(10, null, null);
+ PropertyMaps maps = new PropertyMaps(10, null, null, null);
assertTrue(maps.isEmpty());
assertEquals(0, maps.getSize());
assertFalse(maps.iterator().hasNext());
@@ -59,10 +61,10 @@ public class PropertiesTest extends TestCase
assertTrue(colMap.isEmpty());
assertEquals(0, colMap.getSize());
assertFalse(colMap.iterator().hasNext());
-
+
assertFalse(maps.isEmpty());
assertEquals(2, maps.getSize());
-
+
assertSame(defMap, maps.get(PropertyMaps.DEFAULT_NAME));
assertEquals(PropertyMaps.DEFAULT_NAME, defMap.getName());
assertSame(colMap, maps.get("TESTCOL"));
@@ -97,25 +99,25 @@ public class PropertiesTest extends TestCase
}
}
- assertEquals(Arrays.asList(defMap.get("foo"), defMap.get("baz"),
+ assertEquals(Arrays.asList(defMap.get("foo"), defMap.get("baz"),
colMap.get("buzz")), props);
}
public void testInferTypes() throws Exception
{
- PropertyMaps maps = new PropertyMaps(10, null, null);
+ PropertyMaps maps = new PropertyMaps(10, null, null, null);
PropertyMap defMap = maps.getDefault();
- assertEquals(DataType.TEXT,
+ assertEquals(DataType.TEXT,
defMap.put(PropertyMap.FORMAT_PROP, null).getType());
- assertEquals(DataType.BOOLEAN,
+ assertEquals(DataType.BOOLEAN,
defMap.put(PropertyMap.REQUIRED_PROP, null).getType());
- assertEquals(DataType.TEXT,
+ assertEquals(DataType.TEXT,
defMap.put("strprop", "this is a string").getType());
- assertEquals(DataType.BOOLEAN,
+ assertEquals(DataType.BOOLEAN,
defMap.put("boolprop", true).getType());
- assertEquals(DataType.LONG,
+ assertEquals(DataType.LONG,
defMap.put("intprop", 37).getType());
}
@@ -127,22 +129,22 @@ public class PropertiesTest extends TestCase
Database db = open(testDb);
TableImpl t = (TableImpl)db.getTable("Table1");
- assertEquals(t.getTableDefPageNumber(),
+ assertEquals(t.getTableDefPageNumber(),
t.getPropertyMaps().getObjectId());
PropertyMap tProps = t.getProperties();
assertEquals(PropertyMaps.DEFAULT_NAME, tProps.getName());
int expectedNumProps = 3;
if(db.getFileFormat() != Database.FileFormat.V1997) {
- assertEquals("{5A29A676-1145-4D1A-AE47-9F5415CDF2F1}",
+ assertEquals("{5A29A676-1145-4D1A-AE47-9F5415CDF2F1}",
tProps.getValue(PropertyMap.GUID_PROP));
if(nameMapBytes == null) {
nameMapBytes = (byte[])tProps.getValue("NameMap");
} else {
- assertTrue(Arrays.equals(nameMapBytes,
+ assertTrue(Arrays.equals(nameMapBytes,
(byte[])tProps.getValue("NameMap")));
}
expectedNumProps += 2;
- }
+ }
assertEquals(expectedNumProps, tProps.getSize());
assertEquals((byte)0, tProps.getValue("Orientation"));
assertEquals(Boolean.FALSE, tProps.getValue("OrderByOn"));
@@ -152,7 +154,7 @@ public class PropertiesTest extends TestCase
assertEquals("A", colProps.getName());
expectedNumProps = 9;
if(db.getFileFormat() != Database.FileFormat.V1997) {
- assertEquals("{E9EDD90C-CE55-4151-ABE1-A1ACE1007515}",
+ assertEquals("{E9EDD90C-CE55-4151-ABE1-A1ACE1007515}",
colProps.getValue(PropertyMap.GUID_PROP));
++expectedNumProps;
}
@@ -160,9 +162,9 @@ public class PropertiesTest extends TestCase
assertEquals((short)-1, colProps.getValue("ColumnWidth"));
assertEquals((short)0, colProps.getValue("ColumnOrder"));
assertEquals(Boolean.FALSE, colProps.getValue("ColumnHidden"));
- assertEquals(Boolean.FALSE,
+ assertEquals(Boolean.FALSE,
colProps.getValue(PropertyMap.REQUIRED_PROP));
- assertEquals(Boolean.FALSE,
+ assertEquals(Boolean.FALSE,
colProps.getValue(PropertyMap.ALLOW_ZERO_LEN_PROP));
assertEquals((short)109, colProps.getValue("DisplayControl"));
assertEquals(Boolean.TRUE, colProps.getValue("UnicodeCompression"));
@@ -210,7 +212,8 @@ public class PropertiesTest extends TestCase
for(Row row : ((DatabaseImpl)db).getSystemCatalog()) {
int id = row.getInt("Id");
byte[] propBytes = row.getBytes("LvProp");
- PropertyMaps propMaps = ((DatabaseImpl)db).getPropertiesForObject(id);
+ PropertyMaps propMaps = ((DatabaseImpl)db).getPropertiesForObject(
+ id, null);
int byteLen = ((propBytes != null) ? propBytes.length : 0);
if(byteLen == 0) {
assertTrue(propMaps.isEmpty());
@@ -251,12 +254,12 @@ public class PropertiesTest extends TestCase
checkProperties(propMap, propMap2);
}
-
+
assertFalse(iter.hasNext());
assertFalse(iter2.hasNext());
db.close();
- }
+ }
}
public void testModifyProperties() throws Exception
@@ -333,7 +336,7 @@ public class PropertiesTest extends TestCase
assertTrue((Boolean)cProps.getValue(PropertyMap.REQUIRED_PROP));
assertEquals("42", fProps.getValue(PropertyMap.DEFAULT_VALUE_PROP));
- assertNull(dProps.getValue("DisplayControl"));
+ assertNull(dProps.getValue("DisplayControl"));
cProps.put(PropertyMap.REQUIRED_PROP, DataType.BOOLEAN, false);
fProps.get(PropertyMap.DEFAULT_VALUE_PROP).setValue("0");
@@ -355,7 +358,7 @@ public class PropertiesTest extends TestCase
// weirdo format, no properties
continue;
}
-
+
File file = TestUtil.createTempFile(false);
Database db = new DatabaseBuilder(file)
.setFileFormat(ff)
@@ -380,16 +383,16 @@ public class PropertiesTest extends TestCase
db.close();
db = new DatabaseBuilder(file).open();
-
+
assertEquals("123", db.getUserDefinedProperties().getValue("testing"));
t = db.getTable("Test");
- assertEquals(Boolean.TRUE,
+ assertEquals(Boolean.TRUE,
t.getProperties().getValue("awesome_table"));
Column c = t.getColumn("id");
- assertEquals(Boolean.TRUE,
+ assertEquals(Boolean.TRUE,
c.getProperties().getValue(PropertyMap.REQUIRED_PROP));
assertEquals("{" + u1.toString().toUpperCase() + "}",
c.getProperties().getValue(PropertyMap.GUID_PROP));
@@ -397,13 +400,123 @@ public class PropertiesTest extends TestCase
c = t.getColumn("data");
assertEquals(Boolean.FALSE,
c.getProperties().getValue(PropertyMap.ALLOW_ZERO_LEN_PROP));
- assertEquals("{" + u2.toString().toUpperCase() + "}",
+ assertEquals("{" + u2.toString().toUpperCase() + "}",
c.getProperties().getValue(PropertyMap.GUID_PROP));
}
}
- private static void checkProperties(PropertyMap propMap1,
+ public void testEnforceProperties() throws Exception
+ {
+ for(final FileFormat fileFormat : SUPPORTED_FILEFORMATS) {
+ Database db = create(fileFormat);
+
+ Table t = new TableBuilder("testReq")
+ .addColumn(new ColumnBuilder("id", DataType.LONG)
+ .setAutoNumber(true)
+ .putProperty(PropertyMap.REQUIRED_PROP, true))
+ .addColumn(new ColumnBuilder("value", DataType.TEXT)
+ .putProperty(PropertyMap.REQUIRED_PROP, true))
+ .toTable(db);
+
+ t.addRow(Column.AUTO_NUMBER, "v1");
+
+ try {
+ t.addRow(Column.AUTO_NUMBER, null);
+ fail("InvalidValueException should have been thrown");
+ } catch(InvalidValueException expected) {
+ // success
+ }
+
+ t.addRow(Column.AUTO_NUMBER, "");
+
+ List<? extends Map<String, Object>> expectedRows =
+ createExpectedTable(
+ createExpectedRow(
+ "id", 1,
+ "value", "v1"),
+ createExpectedRow(
+ "id", 2,
+ "value", ""));
+ assertTable(expectedRows, t);
+
+
+ t = new TableBuilder("testNz")
+ .addColumn(new ColumnBuilder("id", DataType.LONG)
+ .setAutoNumber(true)
+ .putProperty(PropertyMap.REQUIRED_PROP, true))
+ .addColumn(new ColumnBuilder("value", DataType.TEXT)
+ .putProperty(PropertyMap.ALLOW_ZERO_LEN_PROP, false))
+ .toTable(db);
+
+ t.addRow(Column.AUTO_NUMBER, "v1");
+
+ try {
+ t.addRow(Column.AUTO_NUMBER, "");
+ fail("InvalidValueException should have been thrown");
+ } catch(InvalidValueException expected) {
+ // success
+ }
+
+ t.addRow(Column.AUTO_NUMBER, null);
+
+ expectedRows =
+ createExpectedTable(
+ createExpectedRow(
+ "id", 1,
+ "value", "v1"),
+ createExpectedRow(
+ "id", 2,
+ "value", null));
+ assertTable(expectedRows, t);
+
+
+ t = new TableBuilder("testReqNz")
+ .addColumn(new ColumnBuilder("id", DataType.LONG)
+ .setAutoNumber(true)
+ .putProperty(PropertyMap.REQUIRED_PROP, true))
+ .addColumn(new ColumnBuilder("value", DataType.TEXT))
+ .toTable(db);
+
+ Column col = t.getColumn("value");
+ PropertyMap props = col.getProperties();
+ props.put(PropertyMap.REQUIRED_PROP, true);
+ props.put(PropertyMap.ALLOW_ZERO_LEN_PROP, false);
+ props.save();
+
+ t.addRow(Column.AUTO_NUMBER, "v1");
+
+ try {
+ t.addRow(Column.AUTO_NUMBER, "");
+ fail("InvalidValueException should have been thrown");
+ } catch(InvalidValueException expected) {
+ // success
+ }
+
+ try {
+ t.addRow(Column.AUTO_NUMBER, null);
+ fail("InvalidValueException should have been thrown");
+ } catch(InvalidValueException expected) {
+ // success
+ }
+
+ t.addRow(Column.AUTO_NUMBER, "v2");
+
+ expectedRows =
+ createExpectedTable(
+ createExpectedRow(
+ "id", 1,
+ "value", "v1"),
+ createExpectedRow(
+ "id", 2,
+ "value", "v2"));
+ assertTable(expectedRows, t);
+
+ db.close();
+ }
+ }
+
+ private static void checkProperties(PropertyMap propMap1,
PropertyMap propMap2)
{
assertEquals(propMap1.getSize(), propMap2.getSize());