git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@816 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-2.0.1
@@ -14,6 +14,9 @@ | |||
<action dev="jahlborn" type="fix"> | |||
Make reading long value columns more lenient (MEMO/OLE). | |||
</action> | |||
<action dev="jahlborn" type="add"> | |||
Add support for modifying properties. | |||
</action> | |||
</release> | |||
<release version="2.0.0" date="2013-08-26"> | |||
<action dev="jahlborn" type="update"> |
@@ -0,0 +1,235 @@ | |||
/* | |||
Copyright (c) 2013 James Ahlborn | |||
This library is free software; you can redistribute it and/or | |||
modify it under the terms of the GNU Lesser General Public | |||
License as published by the Free Software Foundation; either | |||
version 2.1 of the License, or (at your option) any later version. | |||
This library is distributed in the hope that it will be useful, | |||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
Lesser General Public License for more details. | |||
You should have received a copy of the GNU Lesser General Public | |||
License along with this library; if not, write to the Free Software | |||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | |||
USA | |||
*/ | |||
package com.healthmarketscience.jackcess.impl; | |||
import java.nio.ByteBuffer; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
/** | |||
* Utility class for constructing {@code byte[]s} where the final size of the | |||
* data is not known beforehand. The API is similar to {@code ByteBuffer} but | |||
* the data is not actually written to a {@code byte[]} until {@link | |||
* #toBuffer} or {@link #toArray} is called. | |||
* | |||
* @author James Ahlborn | |||
*/ | |||
public class ByteArrayBuilder | |||
{ | |||
private int _pos; | |||
private final List<Data> _data = new ArrayList<Data>(); | |||
public ByteArrayBuilder() { | |||
} | |||
public int position() { | |||
return _pos; | |||
} | |||
public ByteArrayBuilder reserveInt() { | |||
return reserve(4); | |||
} | |||
public ByteArrayBuilder reserveShort() { | |||
return reserve(2); | |||
} | |||
public ByteArrayBuilder reserve(int bytes) { | |||
_pos += bytes; | |||
return this; | |||
} | |||
public ByteArrayBuilder put(byte val) { | |||
return put(new ByteData(_pos, val)); | |||
} | |||
public ByteArrayBuilder putInt(int val) { | |||
return putInt(_pos, val); | |||
} | |||
public ByteArrayBuilder putInt(int pos, int val) { | |||
return put(new IntData(pos, val)); | |||
} | |||
public ByteArrayBuilder putShort(short val) { | |||
return putShort(_pos, val); | |||
} | |||
public ByteArrayBuilder putShort(int pos, short val) { | |||
return put(new ShortData(pos, val)); | |||
} | |||
public ByteArrayBuilder put(byte[] val) { | |||
return put(new BytesData(_pos, val)); | |||
} | |||
public ByteArrayBuilder put(ByteBuffer val) { | |||
return put(new BufData(_pos, val)); | |||
} | |||
private ByteArrayBuilder put(Data data) { | |||
_data.add(data); | |||
int endPos = data.getEndPos(); | |||
if(endPos > _pos) { | |||
_pos = endPos; | |||
} | |||
return this; | |||
} | |||
public ByteBuffer toBuffer() { | |||
return toBuffer(PageChannel.wrap(new byte[_pos])); | |||
} | |||
public ByteBuffer toBuffer(ByteBuffer buf) { | |||
for(Data data : _data) { | |||
data.write(buf); | |||
} | |||
buf.rewind(); | |||
return buf; | |||
} | |||
public byte[] toArray() { | |||
return toBuffer().array(); | |||
} | |||
private static abstract class Data | |||
{ | |||
private int _pos; | |||
protected Data(int pos) { | |||
_pos = pos; | |||
} | |||
public int getPos() { | |||
return _pos; | |||
} | |||
public int getEndPos() { | |||
return getPos() + size(); | |||
} | |||
public abstract int size(); | |||
public abstract void write(ByteBuffer buf); | |||
} | |||
private static final class IntData extends Data | |||
{ | |||
private final int _val; | |||
private IntData(int pos, int val) { | |||
super(pos); | |||
_val = val; | |||
} | |||
@Override | |||
public int size() { | |||
return 4; | |||
} | |||
@Override | |||
public void write(ByteBuffer buf) { | |||
buf.putInt(getPos(), _val); | |||
} | |||
} | |||
private static final class ShortData extends Data | |||
{ | |||
private final short _val; | |||
private ShortData(int pos, short val) { | |||
super(pos); | |||
_val = val; | |||
} | |||
@Override | |||
public int size() { | |||
return 2; | |||
} | |||
@Override | |||
public void write(ByteBuffer buf) { | |||
buf.putShort(getPos(), _val); | |||
} | |||
} | |||
private static final class ByteData extends Data | |||
{ | |||
private final byte _val; | |||
private ByteData(int pos, byte val) { | |||
super(pos); | |||
_val = val; | |||
} | |||
@Override | |||
public int size() { | |||
return 1; | |||
} | |||
@Override | |||
public void write(ByteBuffer buf) { | |||
buf.put(getPos(), _val); | |||
} | |||
} | |||
private static final class BytesData extends Data | |||
{ | |||
private final byte[] _val; | |||
private BytesData(int pos, byte[] val) { | |||
super(pos); | |||
_val = val; | |||
} | |||
@Override | |||
public int size() { | |||
return _val.length; | |||
} | |||
@Override | |||
public void write(ByteBuffer buf) { | |||
buf.position(getPos()); | |||
buf.put(_val); | |||
} | |||
} | |||
private static final class BufData extends Data | |||
{ | |||
private final ByteBuffer _val; | |||
private BufData(int pos, ByteBuffer val) { | |||
super(pos); | |||
_val = val; | |||
} | |||
@Override | |||
public int size() { | |||
return _val.remaining(); | |||
} | |||
@Override | |||
public void write(ByteBuffer buf) { | |||
buf.position(getPos()); | |||
buf.put(_val); | |||
} | |||
} | |||
} |
@@ -1016,35 +1016,41 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { | |||
throws IOException | |||
{ | |||
Matcher m = GUID_PATTERN.matcher(toCharSequence(value)); | |||
if(m.matches()) { | |||
ByteBuffer origBuffer = null; | |||
byte[] tmpBuf = null; | |||
if(order != ByteOrder.BIG_ENDIAN) { | |||
// write to a temp buf so we can do some swapping below | |||
origBuffer = buffer; | |||
tmpBuf = new byte[16]; | |||
buffer = ByteBuffer.wrap(tmpBuf); | |||
} | |||
if(!m.matches()) { | |||
throw new IOException("Invalid GUID: " + value); | |||
} | |||
ByteUtil.writeHexString(buffer, m.group(1)); | |||
ByteUtil.writeHexString(buffer, m.group(2)); | |||
ByteUtil.writeHexString(buffer, m.group(3)); | |||
ByteUtil.writeHexString(buffer, m.group(4)); | |||
ByteUtil.writeHexString(buffer, m.group(5)); | |||
if(tmpBuf != null) { | |||
// the first 3 guid components are integer components which need to | |||
// respect endianness, so swap 4-byte int, 2-byte int, 2-byte int | |||
ByteUtil.swap4Bytes(tmpBuf, 0); | |||
ByteUtil.swap2Bytes(tmpBuf, 4); | |||
ByteUtil.swap2Bytes(tmpBuf, 6); | |||
origBuffer.put(tmpBuf); | |||
} | |||
ByteBuffer origBuffer = null; | |||
byte[] tmpBuf = null; | |||
if(order != ByteOrder.BIG_ENDIAN) { | |||
// write to a temp buf so we can do some swapping below | |||
origBuffer = buffer; | |||
tmpBuf = new byte[16]; | |||
buffer = ByteBuffer.wrap(tmpBuf); | |||
} | |||
} else { | |||
throw new IOException("Invalid GUID: " + value); | |||
ByteUtil.writeHexString(buffer, m.group(1)); | |||
ByteUtil.writeHexString(buffer, m.group(2)); | |||
ByteUtil.writeHexString(buffer, m.group(3)); | |||
ByteUtil.writeHexString(buffer, m.group(4)); | |||
ByteUtil.writeHexString(buffer, m.group(5)); | |||
if(tmpBuf != null) { | |||
// the first 3 guid components are integer components which need to | |||
// respect endianness, so swap 4-byte int, 2-byte int, 2-byte int | |||
ByteUtil.swap4Bytes(tmpBuf, 0); | |||
ByteUtil.swap2Bytes(tmpBuf, 4); | |||
ByteUtil.swap2Bytes(tmpBuf, 6); | |||
origBuffer.put(tmpBuf); | |||
} | |||
} | |||
/** | |||
* Returns {@code true} if the given value is a "guid" value. | |||
*/ | |||
static boolean isGUIDValue(Object value) throws IOException { | |||
return GUID_PATTERN.matcher(toCharSequence(value)).matches(); | |||
} | |||
/** | |||
* Write an LVAL column into a ByteBuffer inline if it fits, otherwise in |
@@ -263,6 +263,7 @@ public abstract class JetFormat { | |||
public final Charset CHARSET; | |||
public final ColumnImpl.SortOrder DEFAULT_SORT_ORDER; | |||
public final byte[] PROPERTY_MAP_TYPE; | |||
/** | |||
* @param channel the database file. | |||
@@ -397,6 +398,7 @@ public abstract class JetFormat { | |||
CHARSET = defineCharset(); | |||
DEFAULT_SORT_ORDER = defineDefaultSortOrder(); | |||
PROPERTY_MAP_TYPE = definePropMapType(); | |||
} | |||
protected abstract boolean defineReadOnly(); | |||
@@ -497,6 +499,7 @@ public abstract class JetFormat { | |||
protected abstract Charset defineCharset(); | |||
protected abstract ColumnImpl.SortOrder defineDefaultSortOrder(); | |||
protected abstract byte[] definePropMapType(); | |||
protected abstract boolean defineLegacyNumericIndexes(); | |||
@@ -715,6 +718,11 @@ public abstract class JetFormat { | |||
return ColumnImpl.GENERAL_LEGACY_SORT_ORDER; | |||
} | |||
@Override | |||
protected byte[] definePropMapType() { | |||
return PROPERTY_MAP_TYPES[1]; | |||
} | |||
@Override | |||
protected Map<String,Database.FileFormat> getPossibleFileFormats() | |||
{ | |||
@@ -935,6 +943,11 @@ public abstract class JetFormat { | |||
return ColumnImpl.GENERAL_LEGACY_SORT_ORDER; | |||
} | |||
@Override | |||
protected byte[] definePropMapType() { | |||
return PROPERTY_MAP_TYPES[0]; | |||
} | |||
@Override | |||
protected Map<String,Database.FileFormat> getPossibleFileFormats() | |||
{ | |||
@@ -1009,6 +1022,11 @@ public abstract class JetFormat { | |||
return ColumnImpl.GENERAL_SORT_ORDER; | |||
} | |||
@Override | |||
protected byte[] definePropMapType() { | |||
return PROPERTY_MAP_TYPES[0]; | |||
} | |||
@Override | |||
protected Map<String,Database.FileFormat> getPossibleFileFormats() { | |||
return PossibleFileFormats.POSSIBLE_VERSION_14; |
@@ -37,10 +37,12 @@ public class PropertyMapImpl implements PropertyMap | |||
private final short _mapType; | |||
private final Map<String,Property> _props = | |||
new LinkedHashMap<String,Property>(); | |||
private final PropertyMaps _owner; | |||
public PropertyMapImpl(String name, short type) { | |||
public PropertyMapImpl(String name, short type, PropertyMaps owner) { | |||
_mapName = name; | |||
_mapType = type; | |||
_owner = owner; | |||
} | |||
public String getName() { | |||
@@ -51,6 +53,10 @@ public class PropertyMapImpl implements PropertyMap | |||
return _mapType; | |||
} | |||
public PropertyMaps getOwner() { | |||
return _owner; | |||
} | |||
public int getSize() { | |||
return _props.size(); | |||
} | |||
@@ -88,24 +94,6 @@ public class PropertyMapImpl implements PropertyMap | |||
return _props.values().iterator(); | |||
} | |||
public PropertyMapImpl merge(PropertyMapImpl opm) { | |||
if(opm == null) { | |||
return this; | |||
} | |||
// merge into least map type | |||
PropertyMapImpl dest = opm; | |||
PropertyMapImpl src = this; | |||
if(dest._mapType < src._mapType) { | |||
dest = this; | |||
src = opm; | |||
} | |||
dest._props.putAll(src._props); | |||
return dest; | |||
} | |||
@Override | |||
public String toString() { | |||
@@ -126,7 +114,7 @@ public class PropertyMapImpl implements PropertyMap | |||
/** | |||
* Info about a property defined in a PropertyMap. | |||
*/ | |||
private static final class PropertyImpl implements PropertyMap.Property | |||
static final class PropertyImpl implements PropertyMap.Property | |||
{ | |||
private final String _name; | |||
private final DataType _type; | |||
@@ -152,6 +140,10 @@ public class PropertyMapImpl implements PropertyMap | |||
return _value; | |||
} | |||
public byte getFlag() { | |||
return _flag; | |||
} | |||
@Override | |||
public String toString() { | |||
Object val = getValue(); |
@@ -25,11 +25,13 @@ import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.Iterator; | |||
import java.util.LinkedHashMap; | |||
import java.util.LinkedHashSet; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import com.healthmarketscience.jackcess.PropertyMap; | |||
import com.healthmarketscience.jackcess.DataType; | |||
import com.healthmarketscience.jackcess.PropertyMap; | |||
/** | |||
* Collection of PropertyMap instances read from a single property data block. | |||
@@ -50,9 +52,11 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
private final Map<String,PropertyMapImpl> _maps = | |||
new LinkedHashMap<String,PropertyMapImpl>(); | |||
private final int _objectId; | |||
private final Handler _handler; | |||
public PropertyMaps(int objectId) { | |||
public PropertyMaps(int objectId, Handler handler) { | |||
_objectId = objectId; | |||
_handler = handler; | |||
} | |||
public int getObjectId() { | |||
@@ -91,24 +95,20 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
String lookupName = DatabaseImpl.toLookupName(name); | |||
PropertyMapImpl map = _maps.get(lookupName); | |||
if(map == null) { | |||
map = new PropertyMapImpl(name, type); | |||
map = new PropertyMapImpl(name, type, this); | |||
_maps.put(lookupName, map); | |||
} | |||
return map; | |||
} | |||
/** | |||
* Adds the given PropertyMap to this group. | |||
*/ | |||
public void put(PropertyMapImpl map) { | |||
String mapName = DatabaseImpl.toLookupName(map.getName()); | |||
_maps.put(mapName, map.merge(_maps.get(mapName))); | |||
} | |||
public Iterator<PropertyMapImpl> iterator() { | |||
return _maps.values().iterator(); | |||
} | |||
public byte[] write() throws IOException { | |||
return _handler.write(this); | |||
} | |||
@Override | |||
public String toString() { | |||
return CustomToStringStyle.builder(this) | |||
@@ -138,14 +138,12 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
public PropertyMaps read(byte[] propBytes, int objectId) | |||
throws IOException | |||
{ | |||
PropertyMaps maps = new PropertyMaps(objectId); | |||
PropertyMaps maps = new PropertyMaps(objectId, this); | |||
if((propBytes == null) || (propBytes.length == 0)) { | |||
return maps; | |||
} | |||
ByteBuffer bb = ByteBuffer.wrap(propBytes) | |||
.order(PageChannel.DEFAULT_BYTE_ORDER); | |||
ByteBuffer bb = PageChannel.wrap(propBytes); | |||
// check for known header | |||
boolean knownType = false; | |||
@@ -176,7 +174,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
if(type == PROPERTY_NAME_LIST) { | |||
propNames = readPropertyNames(bbBlock); | |||
} else { | |||
maps.put(readPropertyValues(bbBlock, propNames, type)); | |||
readPropertyValues(bbBlock, propNames, type, maps); | |||
} | |||
bb.position(endPos); | |||
@@ -185,6 +183,58 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
return maps; | |||
} | |||
/** | |||
* @return a byte[] encoded from the given PropertyMaps instance | |||
*/ | |||
public byte[] write(PropertyMaps maps) | |||
throws IOException | |||
{ | |||
if(maps == null) { | |||
return null; | |||
} | |||
ByteArrayBuilder bab = new ByteArrayBuilder(); | |||
bab.put(_database.getFormat().PROPERTY_MAP_TYPE); | |||
// grab the property names from all the maps | |||
Set<String> propNames = new LinkedHashSet<String>(); | |||
for(PropertyMapImpl propMap : maps) { | |||
for(PropertyMap.Property prop : propMap) { | |||
propNames.add(prop.getName()); | |||
} | |||
} | |||
// write the full set of property names | |||
writeBlock(null, propNames, PROPERTY_NAME_LIST, bab); | |||
// write all the map values | |||
for(PropertyMapImpl propMap : maps) { | |||
writeBlock(propMap, propNames, propMap.getType(), bab); | |||
} | |||
return bab.toArray(); | |||
} | |||
private void writeBlock( | |||
PropertyMapImpl propMap, Set<String> propNames, | |||
short blockType, ByteArrayBuilder bab) | |||
throws IOException | |||
{ | |||
int blockStartPos = bab.position(); | |||
bab.reserveInt() | |||
.putShort(blockType); | |||
if(blockType == PROPERTY_NAME_LIST) { | |||
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 | |||
*/ | |||
@@ -196,12 +246,20 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
return names; | |||
} | |||
private void writePropertyNames(Set<String> propNames, | |||
ByteArrayBuilder bab) { | |||
for(String propName : propNames) { | |||
writePropName(propName, bab); | |||
} | |||
} | |||
/** | |||
* @return the PropertyMap created from the values parsed from the given | |||
* data chunk combined with the given property names | |||
*/ | |||
private PropertyMapImpl readPropertyValues( | |||
ByteBuffer bbBlock, List<String> propNames, short blockType) | |||
ByteBuffer bbBlock, List<String> propNames, short blockType, | |||
PropertyMaps maps) | |||
throws IOException | |||
{ | |||
String mapName = DEFAULT_NAME; | |||
@@ -217,12 +275,12 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
bbBlock.position(endPos); | |||
} | |||
PropertyMapImpl map = new PropertyMapImpl(mapName, blockType); | |||
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; | |||
byte flag = bbBlock.get(); | |||
DataType dataType = DataType.fromByte(bbBlock.get()); | |||
@@ -230,7 +288,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
int dataSize = bbBlock.getShort(); | |||
String propName = propNames.get(nameIdx); | |||
PropColumn col = getColumn(dataType, propName, dataSize); | |||
PropColumn col = getColumn(dataType, propName, dataSize, null); | |||
byte[] data = ByteUtil.getBytes(bbBlock, dataSize); | |||
Object value = col.read(data); | |||
@@ -243,6 +301,54 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
return map; | |||
} | |||
private void writePropertyValues( | |||
PropertyMapImpl propMap, Set<String> propNames, ByteArrayBuilder bab) | |||
throws IOException | |||
{ | |||
// write the map name, if any | |||
String mapName = propMap.getName(); | |||
int blockStartPos = bab.position(); | |||
bab.reserveInt(); | |||
writePropName(mapName, bab); | |||
int len = bab.position() - blockStartPos; | |||
bab.putInt(blockStartPos, len); | |||
// write the map values | |||
int nameIdx = 0; | |||
for(String propName : propNames) { | |||
PropertyMapImpl.PropertyImpl prop = (PropertyMapImpl.PropertyImpl) | |||
propMap.get(propName); | |||
if(prop != null) { | |||
Object value = prop.getValue(); | |||
if(value != null) { | |||
int valStartPos = bab.position(); | |||
bab.reserveShort(); | |||
bab.put(prop.getFlag()); | |||
bab.put(prop.getType().getValue()); | |||
bab.putShort((short)nameIdx); | |||
PropColumn col = getColumn(prop.getType(), propName, -1, value); | |||
ByteBuffer data = col.write( | |||
value, _database.getFormat().MAX_ROW_SIZE); | |||
bab.putShort((short)data.remaining()); | |||
bab.put(data); | |||
len = bab.position() - valStartPos; | |||
bab.putShort(valStartPos, (short)len); | |||
} | |||
} | |||
++nameIdx; | |||
} | |||
} | |||
/** | |||
* Reads a property name from the given data block | |||
*/ | |||
@@ -252,14 +358,26 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
return ColumnImpl.decodeUncompressedText(nameBytes, _database.getCharset()); | |||
} | |||
/** | |||
* Writes a property name to the given data block | |||
*/ | |||
private void writePropName(String propName, ByteArrayBuilder bab) { | |||
ByteBuffer textBuf = ColumnImpl.encodeUncompressedText( | |||
propName, _database.getCharset()); | |||
bab.putShort((short)textBuf.remaining()); | |||
bab.put(textBuf); | |||
} | |||
/** | |||
* Gets a PropColumn capable of reading/writing a property of the given | |||
* DataType | |||
*/ | |||
private PropColumn getColumn(DataType dataType, String propName, | |||
int dataSize) { | |||
int dataSize, Object value) | |||
throws IOException | |||
{ | |||
if(isPseudoGuidColumn(dataType, propName, dataSize)) { | |||
if(isPseudoGuidColumn(dataType, propName, dataSize, value)) { | |||
dataType = DataType.GUID; | |||
} | |||
@@ -278,16 +396,21 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
// create column with ability to read/write the given data type | |||
col = ((colType == DataType.BOOLEAN) ? | |||
new BooleanPropColumn() : new PropColumn(colType)); | |||
_columns.put(dataType, col); | |||
} | |||
return col; | |||
} | |||
private static boolean isPseudoGuidColumn( | |||
DataType dataType, String propName, int dataSize) { | |||
DataType dataType, String propName, int dataSize, Object value) | |||
throws IOException | |||
{ | |||
// guids seem to be marked as "binary" fields | |||
return((dataType == DataType.BINARY) && | |||
(dataSize == DataType.GUID.getFixedSize()) && | |||
((dataSize == DataType.GUID.getFixedSize()) || | |||
((dataSize == -1) && ColumnImpl.isGUIDValue(value))) && | |||
PropertyMap.GUID_PROP.equalsIgnoreCase(propName)); | |||
} | |||
@@ -22,17 +22,19 @@ package com.healthmarketscience.jackcess; | |||
import java.io.File; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import java.util.Map; | |||
import junit.framework.TestCase; | |||
import com.healthmarketscience.jackcess.impl.PropertyMapImpl; | |||
import com.healthmarketscience.jackcess.impl.PropertyMaps; | |||
import static com.healthmarketscience.jackcess.Database.*; | |||
import static com.healthmarketscience.jackcess.DatabaseTest.*; | |||
import com.healthmarketscience.jackcess.impl.ByteUtil; | |||
import com.healthmarketscience.jackcess.impl.DatabaseImpl; | |||
import static com.healthmarketscience.jackcess.impl.JetFormatTest.*; | |||
import com.healthmarketscience.jackcess.impl.PropertyMapImpl; | |||
import com.healthmarketscience.jackcess.impl.PropertyMaps; | |||
import com.healthmarketscience.jackcess.impl.TableImpl; | |||
import com.healthmarketscience.jackcess.impl.DatabaseImpl; | |||
import junit.framework.TestCase; | |||
/** | |||
* @author James Ahlborn | |||
@@ -46,7 +48,7 @@ public class PropertiesTest extends TestCase | |||
public void testPropertyMaps() throws Exception | |||
{ | |||
PropertyMaps maps = new PropertyMaps(10); | |||
PropertyMaps maps = new PropertyMaps(10, null); | |||
assertTrue(maps.isEmpty()); | |||
assertEquals(0, maps.getSize()); | |||
assertFalse(maps.iterator().hasNext()); | |||
@@ -208,4 +210,52 @@ public class PropertiesTest extends TestCase | |||
} | |||
} | |||
public void testWriteProperties() throws Exception | |||
{ | |||
for(TestDB testDb : SUPPORTED_DBS_TEST) { | |||
Database db = open(testDb); | |||
TableImpl t = (TableImpl)db.getTable("Table1"); | |||
PropertyMap tProps = t.getProperties(); | |||
PropertyMaps maps = ((PropertyMapImpl)tProps).getOwner(); | |||
byte[] mapsBytes = maps.write(); | |||
PropertyMaps maps2 = ((DatabaseImpl)db).readProperties( | |||
mapsBytes, maps.getObjectId()); | |||
Iterator<PropertyMapImpl> iter = maps.iterator(); | |||
Iterator<PropertyMapImpl> iter2 = maps2.iterator(); | |||
while(iter.hasNext() && iter2.hasNext()) { | |||
PropertyMapImpl propMap = iter.next(); | |||
PropertyMapImpl propMap2 = iter2.next(); | |||
assertEquals(propMap.getSize(), propMap2.getSize()); | |||
for(PropertyMap.Property prop : propMap) { | |||
PropertyMap.Property prop2 = propMap2.get(prop.getName()); | |||
assertEquals(prop.getName(), prop2.getName()); | |||
assertEquals(prop.getType(), prop2.getType()); | |||
Object v1 = prop.getValue(); | |||
Object v2 = prop2.getValue(); | |||
if(v1 instanceof byte[]) { | |||
assertTrue(Arrays.equals((byte[])v1, (byte[])v2)); | |||
} else { | |||
assertEquals(v1, v2); | |||
} | |||
} | |||
} | |||
assertFalse(iter.hasNext()); | |||
assertFalse(iter2.hasNext()); | |||
db.close(); | |||
} | |||
} | |||
} |