read-only support</role>
</roles>
</contributor>
+ <contributor>
+ <name>Lorenzo Carrara</name>
+ <roles>
+ <role>Reverse engineered the attachment data encoding.</role>
+ </roles>
+ </contributor>
</contributors>
<issueManagement>
<system>SourceForge2</system>
Fix partial page updates when using CodecHandlers which can only do
full page encoding.
</action>
+ <action dev="jahlborn" type="update">
+ Add more methods to Database for retrieving Relationships.
+ </action>
+ <action dev="jahlborn" type="update">
+ Implement attachment decoding, thanks to Lorenzo Carrara.
+ </action>
</release>
<release version="1.2.12" date="2013-05-09">
<action dev="jahlborn" type="fix" system="SourceForge2" issue="94">
public List<Relationship> getRelationships(Table table1, Table table2)
throws IOException;
+ /**
+ * Finds all the relationships in the database for the given table.
+ * @usage _intermediate_method_
+ */
+ public List<Relationship> getRelationships(Table table) throws IOException;
+
+ /**
+ * Finds all the relationships in the database in <i>non-system</i> tables.
+ * </p>
+ * Warning, this may load <i>all</i> the Tables (metadata, not data) in the
+ * database which could cause memory issues.
+ * @usage _intermediate_method_
+ */
+ public List<Relationship> getRelationships() throws IOException;
+
+ /**
+ * Finds <i>all</i> the relationships in the database, <i>including system
+ * tables</i>.
+ * </p>
+ * Warning, this may load <i>all</i> the Tables (metadata, not data) in the
+ * database which could cause memory issues.
+ * @usage _intermediate_method_
+ */
+ public List<Relationship> getSystemRelationships()
+ throws IOException;
+
/**
* Finds all the queries in the database.
* @usage _intermediate_method_
package com.healthmarketscience.jackcess.complex;
+import java.io.IOException;
import java.util.Date;
/**
*/
public interface Attachment extends ComplexValue
{
- public byte[] getFileData();
+ public byte[] getFileData() throws IOException;
public void setFileData(byte[] data);
+ public byte[] getEncodedFileData() throws IOException;
+
+ public void setEncodedFileData(byte[] data);
+
public String getFileName();
public void setFileName(String fileName);
}
@Override
- public String toString()
- {
+ public String toString() {
return String.valueOf(get());
- }
-
+ }
public abstract int get();
Date timeStamp, Integer flags)
throws IOException;
+ public abstract Attachment addEncodedAttachment(byte[] encodedData)
+ throws IOException;
+
+ public abstract Attachment addEncodedAttachment(
+ String url, String name, String type, byte[] encodedData,
+ Date timeStamp, Integer flags)
+ throws IOException;
+
public abstract Attachment updateAttachment(Attachment attachment)
throws IOException;
import java.io.FileWriter;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
* Utility byte stream similar to ByteArrayOutputStream but with extended
* accessibility to the bytes.
*/
- public static class ByteStream
+ public static class ByteStream extends OutputStream
{
private byte[] _bytes;
private int _length;
}
}
+ @Override
public void write(int b) {
ensureNewCapacity(1);
_bytes[_length++] = (byte)b;
}
+ @Override
public void write(byte[] b) {
write(b, 0, b.length);
}
+ @Override
public void write(byte[] b, int offset, int length) {
ensureNewCapacity(length);
System.arraycopy(b, offset, _bytes, _length, length);
Arrays.fill(_bytes, oldLength, _length, b);
}
+ public void skip(int n) {
+ ensureNewCapacity(n);
+ _length += n;
+ }
+
public void writeTo(ByteStream out) {
out.write(_bytes, 0, _length);
}
private byte[] readLongValue(byte[] lvalDefinition)
throws IOException
{
- ByteBuffer def = ByteBuffer.wrap(lvalDefinition)
- .order(PageChannel.DEFAULT_BYTE_ORDER);
+ ByteBuffer def = PageChannel.wrap(lvalDefinition);
int lengthWithFlags = def.getInt();
int length = lengthWithFlags & (~LONG_VALUE_TYPE_MASK);
import com.healthmarketscience.jackcess.ColumnBuilder;
import com.healthmarketscience.jackcess.Cursor;
+import com.healthmarketscience.jackcess.CursorBuilder;
import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.Database;
import com.healthmarketscience.jackcess.IndexBuilder;
import com.healthmarketscience.jackcess.Row;
import com.healthmarketscience.jackcess.RuntimeIOException;
import com.healthmarketscience.jackcess.Table;
-import com.healthmarketscience.jackcess.query.Query;
import com.healthmarketscience.jackcess.impl.query.QueryImpl;
+import com.healthmarketscience.jackcess.query.Query;
import com.healthmarketscience.jackcess.util.CaseInsensitiveColumnMatcher;
import com.healthmarketscience.jackcess.util.ErrorHandler;
import com.healthmarketscience.jackcess.util.LinkResolver;
TableImpl table1, TableImpl table2)
throws IOException
{
- // the relationships table does not get loaded until first accessed
- if(_relationships == null) {
- _relationships = getSystemTable(TABLE_SYSTEM_RELATIONSHIPS);
- if(_relationships == null) {
- throw new IOException("Could not find system relationships table");
- }
- }
-
int nameCmp = table1.getName().compareTo(table2.getName());
if(nameCmp == 0) {
throw new IllegalArgumentException("Must provide two different tables");
table2 = tmp;
}
+ return getRelationshipsImpl(table1, table2, true);
+ }
+
+ public List<Relationship> getRelationships(Table table)
+ throws IOException
+ {
+ if(table == null) {
+ throw new IllegalArgumentException("Must provide a table");
+ }
+ // since we are getting relationships specific to certain table include
+ // 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
+ {
+ // the relationships table does not get loaded until first accessed
+ if(_relationships == null) {
+ _relationships = getSystemTable(TABLE_SYSTEM_RELATIONSHIPS);
+ if(_relationships == null) {
+ throw new IOException("Could not find system relationships table");
+ }
+ }
List<Relationship> relationships = new ArrayList<Relationship>();
+
+ if(table1 != null) {
Cursor cursor = createCursorWithOptionalIndex(
_relationships, REL_COL_FROM_TABLE, table1.getName());
- collectRelationships(cursor, table1, table2, relationships);
+ collectRelationships(cursor, table1, table2, relationships,
+ includeSystemTables);
cursor = createCursorWithOptionalIndex(
_relationships, REL_COL_TO_TABLE, table1.getName());
- collectRelationships(cursor, table2, table1, relationships);
+ collectRelationships(cursor, table2, table1, relationships,
+ includeSystemTables);
+ } else {
+ collectRelationships(new CursorBuilder(_relationships).toCursor(),
+ null, null, relationships, includeSystemTables);
+ }
return relationships;
}
* Finds the relationships matching the given from and to tables from the
* given cursor and adds them to the given list.
*/
- private static void collectRelationships(
+ private void collectRelationships(
Cursor cursor, TableImpl fromTable, TableImpl toTable,
- List<Relationship> relationships)
+ List<Relationship> relationships, boolean includeSystemTables)
+ throws IOException
{
+ String fromTableName = ((fromTable != null) ? fromTable.getName() : null);
+ String toTableName = ((toTable != null) ? toTable.getName() : null);
+
for(Row row : cursor) {
String fromName = (String)row.get(REL_COL_FROM_TABLE);
String toName = (String)row.get(REL_COL_TO_TABLE);
- if(fromTable.getName().equalsIgnoreCase(fromName) &&
- toTable.getName().equalsIgnoreCase(toName))
- {
+ if(((fromTableName == null) ||
+ fromTableName.equalsIgnoreCase(fromName)) &&
+ ((toTableName == null) ||
+ toTableName.equalsIgnoreCase(toName))) {
String relName = (String)row.get(REL_COL_NAME);
}
}
+ TableImpl relFromTable = fromTable;
+ if(relFromTable == null) {
+ relFromTable = getTable(fromName, includeSystemTables);
+ if(relFromTable == null) {
+ // invalid table or ignoring system tables, just ignore
+ continue;
+ }
+ }
+ TableImpl relToTable = toTable;
+ if(relToTable == null) {
+ relToTable = getTable(toName, includeSystemTables);
+ if(relToTable == null) {
+ // invalid table or ignoring system tables, just ignore
+ continue;
+ }
+ }
+
if(rel == null) {
// new relationship
int numCols = (Integer)row.get(REL_COL_COLUMN_COUNT);
int flags = (Integer)row.get(REL_COL_FLAGS);
- rel = new RelationshipImpl(relName, fromTable, toTable,
+ rel = new RelationshipImpl(relName, relFromTable, relToTable,
flags, numCols);
relationships.add(rel);
}
// add column info
int colIdx = (Integer)row.get(REL_COL_COLUMN_INDEX);
- ColumnImpl fromCol = fromTable.getColumn(
+ ColumnImpl fromCol = relFromTable.getColumn(
(String)row.get(REL_COL_FROM_COLUMN));
- ColumnImpl toCol = toTable.getColumn(
+ ColumnImpl toCol = relToTable.getColumn(
(String)row.get(REL_COL_TO_COLUMN));
rel.getFromColumns().set(colIdx, fromCol);
double dateVal = Double.longBitsToDouble(buffer.getLong());
byte[] pwdMask = new byte[4];
- ByteBuffer.wrap(pwdMask).order(PageChannel.DEFAULT_BYTE_ORDER)
- .putInt((int)dateVal);
+ PageChannel.wrap(pwdMask).putInt((int)dateVal);
return pwdMask;
}
}
public Row getObjectRow(Integer parentId, String name,
- Collection<String> columns)
+ Collection<String> columns)
throws IOException
{
Cursor cur = findRow(parentId, name);
.position(position)
.mark();
}
+
+ /**
+ * Returns a ByteBuffer wrapping the given bytes and configured with the
+ * default byte order.
+ */
+ public static ByteBuffer wrap(byte[] bytes) {
+ return ByteBuffer.wrap(bytes).order(DEFAULT_BYTE_ORDER);
+}
}
package com.healthmarketscience.jackcess.impl.complex;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.InflaterInputStream;
import com.healthmarketscience.jackcess.Column;
import com.healthmarketscience.jackcess.Row;
import com.healthmarketscience.jackcess.complex.ComplexValue;
import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
import com.healthmarketscience.jackcess.impl.ByteUtil;
+import com.healthmarketscience.jackcess.impl.ColumnImpl;
+import com.healthmarketscience.jackcess.impl.JetFormat;
+import com.healthmarketscience.jackcess.impl.PageChannel;
/**
public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment>
implements AttachmentColumnInfo
{
+ /** some file formats which may not be worth re-compressing */
+ private static final Set<String> COMPRESSED_FORMATS = new HashSet<String>(
+ Arrays.asList("jpg", "zip", "gz", "bz2", "z", "7z", "cab", "rar",
+ "mp3", "mpg"));
+
private static final String FILE_NAME_COL_NAME = "FileName";
private static final String FILE_TYPE_COL_NAME = "FileType";
+ private static final int DATA_TYPE_RAW = 0;
+ private static final int DATA_TYPE_COMPRESSED = 1;
+
+ private static final int UNKNOWN_HEADER_VAL = 1;
+ private static final int WRAPPER_HEADER_SIZE = 8;
+ private static final int CONTENT_HEADER_SIZE = 12;
+
private final Column _fileUrlCol;
private final Column _fileNameCol;
private final Column _fileTypeCol;
Date ts = (Date)getFileTimeStampColumn().getRowValue(rawValue);
byte[] data = (byte[])getFileDataColumn().getRowValue(rawValue);
- return new AttachmentImpl(id, complexValueFk, url, name, type, data,
- ts, flags);
+ return new AttachmentImpl(id, complexValueFk, url, name, type, null,
+ ts, flags, data);
}
@Override
- protected Object[] asRow(Object[] row, Attachment attachment) {
+ protected Object[] asRow(Object[] row, Attachment attachment)
+ throws IOException
+ {
super.asRow(row, attachment);
getFileUrlColumn().setRowValue(row, attachment.getFileUrl());
getFileNameColumn().setRowValue(row, attachment.getFileName());
getFileTypeColumn().setRowValue(row, attachment.getFileType());
getFileFlagsColumn().setRowValue(row, attachment.getFileFlags());
getFileTimeStampColumn().setRowValue(row, attachment.getFileTimeStamp());
- getFileDataColumn().setRowValue(row, attachment.getFileData());
+ getFileDataColumn().setRowValue(row, attachment.getEncodedFileData());
return row;
}
String type, byte[] data, Date timeStamp, Integer flags)
{
return new AttachmentImpl(INVALID_ID, complexValueFk, url, name, type,
- data, timeStamp, flags);
+ data, timeStamp, flags, null);
}
+ public static Attachment newEncodedAttachment(byte[] encodedData) {
+ return newEncodedAttachment(INVALID_FK, encodedData);
+ }
+
+ public static Attachment newEncodedAttachment(
+ ComplexValueForeignKey complexValueFk, byte[] encodedData) {
+ return newEncodedAttachment(complexValueFk, null, null, null, encodedData,
+ null, null);
+ }
+
+ public static Attachment newEncodedAttachment(
+ String url, String name, String type, byte[] encodedData,
+ Date timeStamp, Integer flags)
+ {
+ return newEncodedAttachment(INVALID_FK, url, name, type,
+ encodedData, timeStamp, flags);
+ }
+
+ public static Attachment newEncodedAttachment(
+ ComplexValueForeignKey complexValueFk, String url, String name,
+ String type, byte[] encodedData, Date timeStamp, Integer flags)
+ {
+ return new AttachmentImpl(INVALID_ID, complexValueFk, url, name, type,
+ null, timeStamp, flags, encodedData);
+ }
+
private static class AttachmentImpl extends ComplexValueImpl
implements Attachment
private byte[] _data;
private Date _timeStamp;
private Integer _flags;
+ private byte[] _encodedData;
private AttachmentImpl(Id id, ComplexValueForeignKey complexValueFk,
String url, String name, String type, byte[] data,
- Date timeStamp, Integer flags)
+ Date timeStamp, Integer flags, byte[] encodedData)
{
super(id, complexValueFk);
_url = url;
_data = data;
_timeStamp = timeStamp;
_flags = flags;
+ _encodedData = encodedData;
}
- public byte[] getFileData() {
+ public byte[] getFileData() throws IOException {
+ if((_data == null) && (_encodedData != null)) {
+ _data = decodeData();
+ }
return _data;
}
public void setFileData(byte[] data) {
_data = data;
+ _encodedData = null;
+ }
+
+ public byte[] getEncodedFileData() throws IOException {
+ if((_encodedData == null) && (_data != null)) {
+ _encodedData = encodeData();
+ }
+ return _encodedData;
+ }
+
+ public void setEncodedFileData(byte[] data) {
+ _encodedData = data;
+ _data = null;
}
public String getFileName() {
}
@Override
- public String toString()
- {
+ public String toString() {
+
+ String dataStr = null;
+ try {
+ dataStr = ByteUtil.toHexString(getFileData());
+ } catch(IOException e) {
+ dataStr = e.toString();
+ }
+
return "Attachment(" + getComplexValueForeignKey() + "," + getId() +
") " + getFileUrl() + ", " + getFileName() + ", " + getFileType()
+ ", " + getFileTimeStamp() + ", " + getFileFlags() + ", " +
- ByteUtil.toHexString(getFileData());
+ dataStr;
}
+
+ /**
+ * Decodes the raw attachment file data to get the _actual_ content.
+ */
+ private byte[] decodeData() throws IOException {
+
+ if(_encodedData.length < WRAPPER_HEADER_SIZE) {
+ // nothing we can do
+ throw new IOException("Unknown encoded attachment data format");
}
+ // read initial header info
+ ByteBuffer bb = PageChannel.wrap(_encodedData);
+ int typeFlag = bb.getInt();
+ int dataLen = bb.getInt();
+
+ DataInputStream contentStream = null;
+ try {
+ InputStream bin = new ByteArrayInputStream(
+ _encodedData, WRAPPER_HEADER_SIZE,
+ _encodedData.length - WRAPPER_HEADER_SIZE);
+
+ if(typeFlag == DATA_TYPE_RAW) {
+ // nothing else to do
+ } else if(typeFlag == DATA_TYPE_COMPRESSED) {
+ // actual content is deflate compressed
+ bin = new InflaterInputStream(bin);
+ } else {
+ throw new IOException(
+ "Unknown encoded attachment data type " + typeFlag);
+}
+
+ contentStream = new DataInputStream(bin);
+
+ // header is an unknown flag followed by the "file extension" of the
+ // data (no clue why we need that again since it's already a separate
+ // field in the attachment table). just skip all of it
+ byte[] tmpBytes = new byte[4];
+ contentStream.readFully(tmpBytes);
+ int headerLen = PageChannel.wrap(tmpBytes).getInt();
+ contentStream.skipBytes(headerLen - 4);
+
+ // calculate actual data length and read it (note, header length
+ // includes the bytes for the length)
+ tmpBytes = new byte[dataLen - headerLen];
+ contentStream.readFully(tmpBytes);
+
+ return tmpBytes;
+
+ } finally {
+ if(contentStream != null) {
+ try {
+ contentStream.close();
+ } catch(IOException e) {
+ // ignored
+ }
+ }
+ }
+ }
+
+ /**
+ * Encodes the actual attachment file data to get the raw, stored format.
+ */
+ private byte[] encodeData() throws IOException {
+
+ // possibly compress data based on file type
+ String type = ((_type != null) ? _type.toLowerCase() : "");
+ boolean shouldCompress = !COMPRESSED_FORMATS.contains(type);
+
+ // encode extension, which ends w/ a null byte
+ type += '\0';
+ ByteBuffer typeBytes = ColumnImpl.encodeUncompressedText(
+ type, JetFormat.VERSION_12.CHARSET);
+ int headerLen = typeBytes.remaining() + CONTENT_HEADER_SIZE;
+
+ int dataLen = _data.length;
+ ByteUtil.ByteStream dataStream = new ByteUtil.ByteStream(
+ WRAPPER_HEADER_SIZE + headerLen + dataLen);
+
+ // write the wrapper header info
+ ByteBuffer bb = PageChannel.wrap(dataStream.getBytes());
+ bb.putInt(shouldCompress ? DATA_TYPE_COMPRESSED : DATA_TYPE_RAW);
+ bb.putInt(dataLen + headerLen);
+ dataStream.skip(WRAPPER_HEADER_SIZE);
+
+ OutputStream contentStream = dataStream;
+ Deflater deflater = null;
+ try {
+
+ if(shouldCompress) {
+ contentStream = new DeflaterOutputStream(
+ contentStream, deflater = new Deflater(3));
+ }
+
+ // write the header w/ the file extension
+ byte[] tmpBytes = new byte[CONTENT_HEADER_SIZE];
+ PageChannel.wrap(tmpBytes)
+ .putInt(headerLen)
+ .putInt(UNKNOWN_HEADER_VAL)
+ .putInt(type.length());
+ contentStream.write(tmpBytes);
+ contentStream.write(typeBytes.array(), 0, typeBytes.remaining());
+
+ // write the _actual_ contents
+ contentStream.write(_data);
+ contentStream.close();
+ contentStream = null;
+
+ return dataStream.toByteArray();
+
+ } finally {
+ if(contentStream != null) {
+ try {
+ contentStream.close();
+ } catch(IOException e) {
+ // ignored
+ }
+ }
+ if(deflater != null) {
+ deflater.end();
+ }
+ }
+ }
+ }
+
}
import java.util.Map;
import com.healthmarketscience.jackcess.Column;
-import com.healthmarketscience.jackcess.CursorBuilder;
import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.Database;
import com.healthmarketscience.jackcess.IndexCursor;
((TableImpl)_flatTable).getRowId(row));
}
- protected Object[] asRow(Object[] row, V value) {
- ComplexValue.Id id = value.getId();
+ protected Object[] asRow(Object[] row, V value)
+ throws IOException
+ {
+ ComplexValue.Id id = value.getId();
_pkCol.setRowValue(
row, ((id != INVALID_ID) ? id : Column.AUTO_NUMBER));
ComplexValueForeignKey cFk = value.getComplexValueForeignKey();
return _value;
}
+ @Override
public Column getColumn() {
return _column;
}
return (UnsupportedColumnInfo)getComplexInfo();
}
+ @Override
public int countValues() throws IOException {
return getComplexInfo().countValues(get());
}
return getComplexInfo().getRawValues(get());
}
+ @Override
public List<? extends ComplexValue> getValues() throws IOException {
if(_values == null) {
_values = getComplexInfo().getValues(this);
return _values;
}
+ @Override
@SuppressWarnings("unchecked")
public List<Version> getVersions() throws IOException {
if(getComplexType() != ComplexDataType.VERSION_HISTORY) {
return (List<Version>)getValues();
}
+ @Override
@SuppressWarnings("unchecked")
public List<Attachment> getAttachments() throws IOException {
if(getComplexType() != ComplexDataType.ATTACHMENT) {
return (List<Attachment>)getValues();
}
+ @Override
@SuppressWarnings("unchecked")
public List<SingleValue> getMultiValues() throws IOException {
if(getComplexType() != ComplexDataType.MULTI_VALUE) {
return (List<SingleValue>)getValues();
}
+ @Override
@SuppressWarnings("unchecked")
public List<UnsupportedValue> getUnsupportedValues() throws IOException {
if(getComplexType() != ComplexDataType.UNSUPPORTED) {
return (List<UnsupportedValue>)getValues();
}
+ @Override
public void reset() {
// discard any cached values
_values = null;
}
+ @Override
public Version addVersion(String value) throws IOException {
return addVersion(value, new Date());
}
+ @Override
public Version addVersion(String value, Date modifiedDate) throws IOException {
reset();
Version v = VersionHistoryColumnInfoImpl.newVersion(this, value, modifiedDate);
return v;
}
+ @Override
public Attachment addAttachment(byte[] data) throws IOException {
return addAttachment(null, null, null, data, null, null);
}
+ @Override
public Attachment addAttachment(
String url, String name, String type, byte[] data,
Date timeStamp, Integer flags)
return a;
}
+ @Override
+ public Attachment addEncodedAttachment(byte[] encodedData)
+ throws IOException
+ {
+ return addEncodedAttachment(null, null, null, encodedData, null, null);
+ }
+
+ @Override
+ public Attachment addEncodedAttachment(
+ String url, String name, String type, byte[] encodedData,
+ Date timeStamp, Integer flags)
+ throws IOException
+ {
+ reset();
+ Attachment a = AttachmentColumnInfoImpl.newEncodedAttachment(
+ this, url, name, type, encodedData, timeStamp, flags);
+ getAttachmentInfo().addValue(a);
+ return a;
+ }
+
+ @Override
public Attachment updateAttachment(Attachment attachment) throws IOException {
reset();
getAttachmentInfo().updateValue(attachment);
return attachment;
}
+ @Override
public Attachment deleteAttachment(Attachment attachment) throws IOException {
reset();
getAttachmentInfo().deleteValue(attachment);
return attachment;
}
+ @Override
public SingleValue addMultiValue(Object value) throws IOException {
reset();
SingleValue v = MultiValueColumnInfoImpl.newSingleValue(this, value);
return v;
}
+ @Override
public SingleValue updateMultiValue(SingleValue value) throws IOException {
reset();
getMultiValueInfo().updateValue(value);
return value;
}
+ @Override
public SingleValue deleteMultiValue(SingleValue value) throws IOException {
reset();
getMultiValueInfo().deleteValue(value);
return value;
}
+ @Override
public UnsupportedValue addUnsupportedValue(Map<String,?> values)
throws IOException
{
return v;
}
+ @Override
public UnsupportedValue updateUnsupportedValue(UnsupportedValue value)
throws IOException
{
return value;
}
+ @Override
public UnsupportedValue deleteUnsupportedValue(UnsupportedValue value)
throws IOException
{
return value;
}
+ @Override
public void deleteAllValues() throws IOException {
reset();
getComplexInfo().deleteAllValues(this);
}
@Override
- protected Object[] asRow(Object[] row, SingleValue value) {
+ protected Object[] asRow(Object[] row, SingleValue value) throws IOException {
super.asRow(row, value);
getValueColumn().setRowValue(row, value.get());
return row;
}
@Override
- protected Object[] asRow(Object[] row, UnsupportedValue value) {
+ protected Object[] asRow(Object[] row, UnsupportedValue value)
+ throws IOException
+ {
super.asRow(row, value);
Map<String,Object> values = value.getValues();
}
@Override
- protected Object[] asRow(Object[] row, Version version) {
+ protected Object[] asRow(Object[] row, Version version) throws IOException {
super.asRow(row, version);
getValueColumn().setRowValue(row, version.getValue());
getModifiedDateColumn().setRowValue(row, version.getModifiedDate());
package com.healthmarketscience.jackcess;
+import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import static com.healthmarketscience.jackcess.DatabaseTest.*;
-import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
import com.healthmarketscience.jackcess.complex.Attachment;
import com.healthmarketscience.jackcess.complex.ComplexDataType;
import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
import com.healthmarketscience.jackcess.complex.SingleValue;
import com.healthmarketscience.jackcess.complex.UnsupportedValue;
import com.healthmarketscience.jackcess.complex.Version;
-import junit.framework.TestCase;
+import com.healthmarketscience.jackcess.impl.ByteUtil;
import com.healthmarketscience.jackcess.impl.ColumnImpl;
+import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
+import com.healthmarketscience.jackcess.impl.PageChannel;
+import junit.framework.TestCase;
/**
row8ValFk.addAttachment(null, "test_data.txt", "txt",
getFileBytes("test_data.txt"), null, null);
checkAttachments(row8ValFk.get(), row8ValFk, "test_data.txt");
+ row8ValFk.addEncodedAttachment(null, "test_data2.txt", "txt",
+ getEncodedFileBytes("test_data2.txt"), null,
+ null);
+ checkAttachments(row8ValFk.get(), row8ValFk, "test_data.txt",
+ "test_data2.txt");
Cursor cursor = CursorBuilder.createCursor(t1);
assertTrue(cursor.findFirstRow(t1.getColumn("id"), "row4"));
null);
checkAttachments(4, row4ValFk, "test_data2.txt", "test_data.txt");
- a.setFileType("xml");
- a.setFileName("some_data.xml");
- byte[] newBytes = "this is not xml".getBytes("US-ASCII");
+ a.setFileType("zip");
+ a.setFileName("some_data.zip");
+ byte[] newBytes = "this is not a zip file".getBytes("US-ASCII");
a.setFileData(newBytes);
a.update();
Attachment updated = row4ValFk.getAttachments().get(1);
assertNotSame(updated, a);
- assertEquals("xml", updated.getFileType());
- assertEquals("some_data.xml", updated.getFileName());
+ assertEquals("zip", updated.getFileType());
+ assertEquals("some_data.zip", updated.getFileName());
assertTrue(Arrays.equals(newBytes, updated.getFileData()));
+ byte[] encBytes = updated.getEncodedFileData();
+ assertEquals(newBytes.length + 28, encBytes.length);
+ ByteBuffer bb = PageChannel.wrap(encBytes);
+ assertEquals(0, bb.getInt());
+ assertTrue(ByteUtil.matchesRange(bb, 28, newBytes));
updated.delete();
checkAttachments(4, row4ValFk, "test_data2.txt");
assertEquals(fileNames.length, attachments.size());
for(int i = 0; i < fileNames.length; ++i) {
String fname = fileNames[i];
- byte[] dataBytes = getFileBytes(fname);
Attachment a = attachments.get(i);
assertEquals(fname, a.getFileName());
assertEquals("txt", a.getFileType());
- assertTrue(Arrays.equals(dataBytes, a.getFileData()));
+ assertTrue(Arrays.equals(getFileBytes(fname), a.getFileData()));
+ assertTrue(Arrays.equals(getEncodedFileBytes(fname),
+ a.getEncodedFileData()));
}
}
}
throw new RuntimeException("unexpected bytes");
}
+ private static byte[] getEncodedFileBytes(String fname) throws Exception
+ {
+ if("test_data.txt".equals(fname)) {
+ return TEST_ENC_BYTES;
+ }
+ if("test_data2.txt".equals(fname)) {
+ return TEST2_ENC_BYTES;
+ }
+ throw new RuntimeException("unexpected bytes");
+ }
+
private static byte b(int i) { return (byte)i; }
- private static final byte[] TEST_BYTES = new byte[] {
+ private static byte[] getAsciiBytes(String str) {
+ try {
+ return str.getBytes("US-ASCII");
+ } catch(Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static final byte[] TEST_ENC_BYTES = new byte[] {
b(0x01),b(0x00),b(0x00),b(0x00),b(0x3A),b(0x00),b(0x00),b(0x00),b(0x78),b(0x5E),b(0x13),b(0x61),b(0x60),b(0x60),b(0x60),b(0x04),b(0x62),b(0x16),b(0x20),b(0x2E),b(0x61),b(0xA8),b(0x00),b(0x62),
b(0x20),b(0x9D),b(0x91),b(0x59),b(0xAC),b(0x00),b(0x44),b(0xC5),b(0xF9),b(0xB9),b(0xA9),b(0x0A),b(0x25),b(0xA9),b(0xC5),b(0x25),b(0x0A),b(0x29),b(0x89),b(0x25),b(0x89),b(0x0A),b(0x69),b(0xF9),
b(0x45),b(0x0A),b(0x89),b(0x25),b(0x25),b(0x89),b(0xC9),b(0x19),b(0xB9),b(0xA9),b(0x79),b(0x25),b(0x7A),b(0x00),b(0x52),b(0xA9),b(0x0F),b(0x7A)
};
+
+ private static final byte[] TEST_BYTES = getAsciiBytes("this is some test data for attachment.");
- private static final byte[] TEST2_BYTES = new byte[] {
+ private static final byte[] TEST2_ENC_BYTES = new byte[] {
b(0x01),b(0x00),b(0x00),b(0x00),b(0x3F),b(0x00),b(0x00),b(0x00),b(0x78),b(0x5E),b(0x13),b(0x61),b(0x60),b(0x60),b(0x60),b(0x04),b(0x62),b(0x16),b(0x20),b(0x2E),b(0x61),b(0xA8),b(0x00),b(0x62),
b(0x20),b(0x9D),b(0x91),b(0x59),b(0xAC),b(0x00),b(0x44),b(0xC5),b(0xF9),b(0xB9),b(0xA9),b(0x0A),b(0xB9),b(0xF9),b(0x45),b(0xA9),b(0x0A),b(0x25),b(0xA9),b(0xC5),b(0x25),b(0x0A),b(0x29),b(0x89),
b(0x25),b(0x89),b(0x0A),b(0x69),b(0xF9),b(0x45),b(0x0A),b(0x89),b(0x25),b(0x25),b(0x89),b(0xC9),b(0x19),b(0xB9),b(0xA9),b(0x79),b(0x25),b(0x7A),b(0x00),b(0xA5),b(0x0B),b(0x11),b(0x4D)
};
+
+ private static final byte[] TEST2_BYTES = getAsciiBytes("this is some more test data for attachment.");
+
}
package com.healthmarketscience.jackcess;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
-import junit.framework.TestCase;
-
import static com.healthmarketscience.jackcess.DatabaseTest.*;
import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
import com.healthmarketscience.jackcess.impl.RelationshipImpl;
+import junit.framework.TestCase;
/**
* @author James Ahlborn
*/
public class RelationshipTest extends TestCase {
+ private static final Comparator<Relationship> REL_COMP = new Comparator<Relationship>() {
+ public int compare(Relationship r1, Relationship r2) {
+ return String.CASE_INSENSITIVE_ORDER.compare(r1.getName(), r2.getName());
+ }
+ };
+
public RelationshipTest(String name) throws Exception {
super(name);
}
- public void testSimple() throws Exception {
+ public void testTwoTables() throws Exception {
for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX, true)) {
Database db = open(testDB);
Table t1 = db.getTable("Table1");
assertTrue(rel.hasReferentialIntegrity());
assertEquals(4096, ((RelationshipImpl)rel).getFlags());
assertTrue(rel.cascadeDeletes());
- assertSameRelationships(rels, db.getRelationships(t2, t1));
+ assertSameRelationships(rels, db.getRelationships(t2, t1), true);
rels = db.getRelationships(t2, t3);
assertTrue(db.getRelationships(t2, t3).isEmpty());
- assertSameRelationships(rels, db.getRelationships(t3, t2));
+ assertSameRelationships(rels, db.getRelationships(t3, t2), true);
rels = db.getRelationships(t1, t3);
assertEquals(1, rels.size());
assertTrue(rel.hasReferentialIntegrity());
assertEquals(256, ((RelationshipImpl)rel).getFlags());
assertTrue(rel.cascadeUpdates());
- assertSameRelationships(rels, db.getRelationships(t3, t1));
+ assertSameRelationships(rels, db.getRelationships(t3, t1), true);
try {
db.getRelationships(t1, t1);
}
}
- private void assertSameRelationships(
- List<Relationship> expected, List<Relationship> found)
+ public void testOneTable() throws Exception {
+ for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX, true)) {
+ Database db = open(testDB);
+ Table t1 = db.getTable("Table1");
+ Table t2 = db.getTable("Table2");
+ Table t3 = db.getTable("Table3");
+
+ List<Relationship> expected = new ArrayList<Relationship>();
+ expected.addAll(db.getRelationships(t1, t2));
+ expected.addAll(db.getRelationships(t2, t3));
+
+ assertSameRelationships(expected, db.getRelationships(t2), false);
+
+ }
+ }
+
+ public void testNoTables() throws Exception {
+ for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX, true)) {
+ Database db = open(testDB);
+ Table t1 = db.getTable("Table1");
+ Table t2 = db.getTable("Table2");
+ Table t3 = db.getTable("Table3");
+
+ List<Relationship> expected = new ArrayList<Relationship>();
+ expected.addAll(db.getRelationships(t1, t2));
+ expected.addAll(db.getRelationships(t2, t3));
+ expected.addAll(db.getRelationships(t1, t3));
+
+ assertSameRelationships(expected, db.getRelationships(), false);
+ }
+ }
+
+ private static void assertSameRelationships(
+ List<Relationship> expected, List<Relationship> found, boolean ordered)
{
assertEquals(expected.size(), found.size());
+ if(!ordered) {
+ Collections.sort(expected, REL_COMP);
+ Collections.sort(found, REL_COMP);
+ }
for(int i = 0; i < expected.size(); ++i) {
Relationship eRel = expected.get(i);
Relationship fRel = found.get(i);