diff options
Diffstat (limited to 'src/main/java')
8 files changed, 822 insertions, 606 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(); } } - + } |