aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/com/healthmarketscience/jackcess/complex
diff options
context:
space:
mode:
authorJames Ahlborn <jtahlborn@yahoo.com>2011-10-26 02:45:20 +0000
committerJames Ahlborn <jtahlborn@yahoo.com>2011-10-26 02:45:20 +0000
commitb014c0d170abc6bdca38078aaf1897aa66125152 (patch)
treeae88feddf4083861ba41314b732c4cb352a1510d /src/java/com/healthmarketscience/jackcess/complex
parentc2c8d0a7be74b1baacd97ce430225270c8208123 (diff)
downloadjackcess-b014c0d170abc6bdca38078aaf1897aa66125152.tar.gz
jackcess-b014c0d170abc6bdca38078aaf1897aa66125152.zip
Add support for reading/writing complex column data (version history, attachments, multi-value columns)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@580 f203690c-595d-4dc9-a70b-905162fa7fd2
Diffstat (limited to 'src/java/com/healthmarketscience/jackcess/complex')
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/Attachment.java53
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java326
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java400
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/ComplexDataType.java29
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/ComplexValue.java43
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java246
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/MultiValueColumnInfo.java143
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/SingleValue.java14
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java63
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/Version.java33
-rw-r--r--src/java/com/healthmarketscience/jackcess/complex/VersionHistoryColumnInfo.java212
11 files changed, 1562 insertions, 0 deletions
diff --git a/src/java/com/healthmarketscience/jackcess/complex/Attachment.java b/src/java/com/healthmarketscience/jackcess/complex/Attachment.java
new file mode 100644
index 0000000..af33544
--- /dev/null
+++ b/src/java/com/healthmarketscience/jackcess/complex/Attachment.java
@@ -0,0 +1,53 @@
+/*
+Copyright (c) 2011 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.complex;
+
+import java.util.Date;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public interface Attachment extends ComplexValue
+{
+ public byte[] getFileData();
+
+ public void setFileData(byte[] data);
+
+ public String getFileName();
+
+ public void setFileName(String fileName);
+
+ public String getFileUrl();
+
+ public void setFileUrl(String fileUrl);
+
+ public String getFileType();
+
+ public void setFileType(String fileType);
+
+ public Date getFileTimeStamp();
+
+ public void setFileTimeStamp(Date fileTimeStamp);
+
+ public Integer getFileFlags();
+
+ public void setFileFlags(Integer fileFlags);
+}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java
new file mode 100644
index 0000000..f258ca0
--- /dev/null
+++ b/src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java
@@ -0,0 +1,326 @@
+/*
+Copyright (c) 2011 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.complex;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import com.healthmarketscience.jackcess.ByteUtil;
+import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.Table;
+
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
+{
+ private static final String FILE_NAME_COL_NAME = "FileName";
+ private static final String FILE_TYPE_COL_NAME = "FileType";
+
+ private final Column _fileUrlCol;
+ private final Column _fileNameCol;
+ private final Column _fileTypeCol;
+ private final Column _fileDataCol;
+ private final Column _fileTimeStampCol;
+ private final Column _fileFlagsCol;
+
+ public AttachmentColumnInfo(Column column, int complexId,
+ Table typeObjTable, Table flatTable)
+ throws IOException
+ {
+ super(column, complexId, typeObjTable, flatTable);
+
+ Column fileUrlCol = null;
+ Column fileNameCol = null;
+ Column fileTypeCol = null;
+ Column fileDataCol = null;
+ Column fileTimeStampCol = null;
+ Column fileFlagsCol = null;
+
+ for(Column col : getTypeColumns()) {
+ switch(col.getType()) {
+ case TEXT:
+ if(FILE_NAME_COL_NAME.equalsIgnoreCase(col.getName())) {
+ fileNameCol = col;
+ } else if(FILE_TYPE_COL_NAME.equalsIgnoreCase(col.getName())) {
+ fileTypeCol = col;
+ } else {
+ // if names don't match, assign in order: name, type
+ if(fileNameCol == null) {
+ fileNameCol = col;
+ } else if(fileTypeCol == null) {
+ fileTypeCol = col;
+ }
+ }
+ break;
+ case LONG:
+ fileFlagsCol = col;
+ break;
+ case SHORT_DATE_TIME:
+ fileTimeStampCol = col;
+ break;
+ case OLE:
+ fileDataCol = col;
+ break;
+ case MEMO:
+ fileUrlCol = col;
+ break;
+ default:
+ // ignore
+ }
+ }
+
+ _fileUrlCol = fileUrlCol;
+ _fileNameCol = fileNameCol;
+ _fileTypeCol = fileTypeCol;
+ _fileDataCol = fileDataCol;
+ _fileTimeStampCol = fileTimeStampCol;
+ _fileFlagsCol = fileFlagsCol;
+ }
+
+ public Column getFileUrlColumn() {
+ return _fileUrlCol;
+ }
+
+ public Column getFileNameColumn() {
+ return _fileNameCol;
+ }
+
+ public Column getFileTypeColumn() {
+ return _fileTypeCol;
+ }
+
+ public Column getFileDataColumn() {
+ return _fileDataCol;
+ }
+
+ public Column getFileTimeStampColumn() {
+ return _fileTimeStampCol;
+ }
+
+ public Column getFileFlagsColumn() {
+ return _fileFlagsCol;
+ }
+
+ @Override
+ public ComplexDataType getType()
+ {
+ return ComplexDataType.ATTACHMENT;
+ }
+
+ @Override
+ protected List<Attachment> toValues(ComplexValueForeignKey complexValueFk,
+ List<Map<String,Object>> rawValues)
+ throws IOException
+ {
+ List<Attachment> attachments = new ArrayList<Attachment>();
+ for(Map<String,Object> rawValue : rawValues) {
+ attachments.add(toAttachment(complexValueFk, rawValue));
+ }
+ return attachments;
+ }
+
+ protected AttachmentImpl toAttachment(ComplexValueForeignKey complexValueFk,
+ Map<String,Object> rawValue) {
+ int id = (Integer)getPrimaryKeyColumn().getRowValue(rawValue);
+ String url = (String)getFileUrlColumn().getRowValue(rawValue);
+ String name = (String)getFileNameColumn().getRowValue(rawValue);
+ String type = (String)getFileTypeColumn().getRowValue(rawValue);
+ Integer flags = (Integer)getFileFlagsColumn().getRowValue(rawValue);
+ Date ts = (Date)getFileTimeStampColumn().getRowValue(rawValue);
+ byte[] data = (byte[])getFileDataColumn().getRowValue(rawValue);
+
+ return new AttachmentImpl(id, complexValueFk, url, name, type, data,
+ ts, flags);
+ }
+
+ @Override
+ protected Object[] asRow(Object[] row, Attachment attachment) {
+ 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());
+ return row;
+ }
+
+ public static Attachment newAttachment(byte[] data) {
+ return newAttachment(INVALID_COMPLEX_VALUE_ID, data);
+ }
+
+ public static Attachment newAttachment(ComplexValueForeignKey complexValueFk,
+ byte[] data) {
+ return newAttachment(complexValueFk, null, null, null, data, null, null);
+ }
+
+ public static Attachment newAttachment(
+ String url, String name, String type, byte[] data,
+ Date timeStamp, Integer flags)
+ {
+ return newAttachment(INVALID_COMPLEX_VALUE_ID, url, name, type, data,
+ timeStamp, flags);
+ }
+
+ public static Attachment newAttachment(
+ ComplexValueForeignKey complexValueFk, String url, String name,
+ String type, byte[] data, Date timeStamp, Integer flags)
+ {
+ return new AttachmentImpl(INVALID_ID, complexValueFk, url, name, type,
+ data, timeStamp, flags);
+ }
+
+
+ public static boolean isAttachmentColumn(Table typeObjTable) {
+ // attachment data has these columns FileURL(MEMO), FileName(TEXT),
+ // FileType(TEXT), FileData(OLE), FileTimeStamp(SHORT_DATE_TIME),
+ // FileFlags(LONG)
+ List<Column> typeCols = typeObjTable.getColumns();
+ if(typeCols.size() < 6) {
+ return false;
+ }
+
+ int numMemo = 0;
+ int numText = 0;
+ int numDate = 0;
+ int numOle= 0;
+ int numLong = 0;
+
+ for(Column col : typeCols) {
+ switch(col.getType()) {
+ case TEXT:
+ ++numText;
+ break;
+ case LONG:
+ ++numLong;
+ break;
+ case SHORT_DATE_TIME:
+ ++numDate;
+ break;
+ case OLE:
+ ++numOle;
+ break;
+ case MEMO:
+ ++numMemo;
+ break;
+ default:
+ // ignore
+ }
+ }
+
+ // be flexible, allow for extra columns...
+ return((numMemo >= 1) && (numText >= 2) && (numOle >= 1) &&
+ (numDate >= 1) && (numLong >= 1));
+ }
+
+
+ private static class AttachmentImpl extends ComplexValueImpl
+ implements Attachment
+ {
+ private String _url;
+ private String _name;
+ private String _type;
+ private byte[] _data;
+ private Date _timeStamp;
+ private Integer _flags;
+
+ private AttachmentImpl(int id, ComplexValueForeignKey complexValueFk,
+ String url, String name, String type, byte[] data,
+ Date timeStamp, Integer flags)
+ {
+ super(id, complexValueFk);
+ _url = url;
+ _name = name;
+ _type = type;
+ _data = data;
+ _timeStamp = timeStamp;
+ _flags = flags;
+ }
+
+ public byte[] getFileData() {
+ return _data;
+ }
+
+ public void setFileData(byte[] data) {
+ _data = data;
+ }
+
+ public String getFileName() {
+ return _name;
+ }
+
+ public void setFileName(String fileName) {
+ _name = fileName;
+ }
+
+ public String getFileUrl() {
+ return _url;
+ }
+
+ public void setFileUrl(String fileUrl) {
+ _url = fileUrl;
+ }
+
+ public String getFileType() {
+ return _type;
+ }
+
+ public void setFileType(String fileType) {
+ _type = fileType;
+ }
+
+ public Date getFileTimeStamp() {
+ return _timeStamp;
+ }
+
+ public void setFileTimeStamp(Date fileTimeStamp) {
+ _timeStamp = fileTimeStamp;
+ }
+
+ public Integer getFileFlags() {
+ return _flags;
+ }
+
+ public void setFileFlags(Integer fileFlags) {
+ _flags = fileFlags;
+ }
+
+ @Override
+ public void update() throws IOException {
+ getComplexValueForeignKey().updateAttachment(this);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Attachment(" + getComplexValueForeignKey() + "," + getId() +
+ ") " + getFileUrl() + ", " + getFileName() + ", " + getFileType()
+ + ", " + getFileTimeStamp() + ", " + getFileFlags() + ", " +
+ ByteUtil.toHexString(getFileData());
+ }
+ }
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java
new file mode 100644
index 0000000..5232637
--- /dev/null
+++ b/src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java
@@ -0,0 +1,400 @@
+/*
+Copyright (c) 2011 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.complex;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+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;
+import com.healthmarketscience.jackcess.JetFormat;
+import com.healthmarketscience.jackcess.PageChannel;
+import com.healthmarketscience.jackcess.Table;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Base class for the additional information tracked for complex columns.
+ *
+ * @author James Ahlborn
+ */
+public abstract class ComplexColumnInfo<V extends ComplexValue>
+{
+ private static final Log LOG = LogFactory.getLog(Column.class);
+
+ public static final int INVALID_ID = -1;
+ public static final ComplexValueForeignKey INVALID_COMPLEX_VALUE_ID =
+ new ComplexValueForeignKey(null, INVALID_ID);
+
+ private static final String COL_COMPLEX_TYPE_OBJECT_ID = "ComplexTypeObjectID";
+ private static final String COL_TABLE_ID = "ConceptualTableID";
+ private static final String COL_FLAT_TABLE_ID = "FlatTableID";
+
+ private final Column _column;
+ private final int _complexTypeId;
+ private final Table _flatTable;
+ private final List<Column> _typeCols;
+ private final Column _pkCol;
+ private final Column _complexValFkCol;
+ private IndexCursor _pkCursor;
+ private IndexCursor _complexValIdCursor;
+
+ protected ComplexColumnInfo(Column column, int complexTypeId,
+ Table typeObjTable, Table flatTable)
+ throws IOException
+ {
+ _column = column;
+ _complexTypeId = complexTypeId;
+ _flatTable = flatTable;
+
+ // the flat table has all the "value" columns and 2 extra columns, a
+ // primary key for each row, and a LONG value which is essentially a
+ // foreign key to the main table.
+ _typeCols = new ArrayList<Column>();
+ List<Column> otherCols = new ArrayList<Column>();
+ diffFlatColumns(typeObjTable, flatTable, _typeCols, otherCols);
+
+ Column pkCol = null;
+ Column complexValFkCol = null;
+ for(Column col : otherCols) {
+ if(col.isAutoNumber()) {
+ pkCol = col;
+ } else if(col.getType() == DataType.LONG) {
+ complexValFkCol = col;
+ }
+ }
+
+ if((pkCol == null) || (complexValFkCol == null)) {
+ throw new IOException("Could not find expected columns in flat table " +
+ flatTable.getName() + " for complex column with id "
+ + complexTypeId);
+ }
+ _pkCol = pkCol;
+ _complexValFkCol = complexValFkCol;
+ }
+
+ public static ComplexColumnInfo<? extends ComplexValue> create(
+ Column column, ByteBuffer buffer, int offset)
+ throws IOException
+ {
+ int complexTypeId = buffer.getInt(
+ offset + column.getFormat().OFFSET_COLUMN_COMPLEX_ID);
+
+ Database db = column.getDatabase();
+ Table complexColumns = db.getSystemComplexColumns();
+ IndexCursor cursor = IndexCursor.createCursor(
+ complexColumns, complexColumns.getPrimaryKeyIndex());
+ if(!cursor.findRowByEntry(complexTypeId)) {
+ throw new IOException(
+ "Could not find complex column info for complex column with id " +
+ complexTypeId);
+ }
+ Map<String,Object> cColRow = cursor.getCurrentRow();
+ int tableId = (Integer)cColRow.get(COL_TABLE_ID);
+ if(tableId != column.getTable().getTableDefPageNumber()) {
+ throw new IOException(
+ "Found complex column for table " + tableId + " but expected table " +
+ column.getTable().getTableDefPageNumber());
+ }
+ int flatTableId = (Integer)cColRow.get(COL_FLAT_TABLE_ID);
+ int typeObjId = (Integer)cColRow.get(COL_COMPLEX_TYPE_OBJECT_ID);
+
+ Table typeObjTable = db.getTable(typeObjId);
+ Table flatTable = db.getTable(flatTableId);
+
+ if((typeObjTable == null) || (flatTable == null)) {
+ throw new IOException(
+ "Could not find supporting tables (" + typeObjId + ", " + flatTableId
+ + ") for complex column with id " + complexTypeId);
+ }
+
+ // we inspect the structore of the "type table" to determine what kind of
+ // complex info we are dealing with
+ if(MultiValueColumnInfo.isMultiValueColumn(typeObjTable)) {
+ return new MultiValueColumnInfo(column, complexTypeId, typeObjTable,
+ flatTable);
+ } else if(AttachmentColumnInfo.isAttachmentColumn(typeObjTable)) {
+ return new AttachmentColumnInfo(column, complexTypeId, typeObjTable,
+ flatTable);
+ } else if(VersionHistoryColumnInfo.isVersionHistoryColumn(typeObjTable)) {
+ return new VersionHistoryColumnInfo(column, complexTypeId, typeObjTable,
+ flatTable);
+ }
+
+ LOG.warn("Unsupported complex column type " + typeObjTable.getName());
+ return new UnsupportedColumnInfo(column, complexTypeId, typeObjTable,
+ flatTable);
+ }
+
+ public void postTableLoadInit() throws IOException {
+ // nothing to do in base class
+ }
+
+ public Column getColumn() {
+ return _column;
+ }
+
+ public Database getDatabase() {
+ return getColumn().getDatabase();
+ }
+
+ public JetFormat getFormat() {
+ return getDatabase().getFormat();
+ }
+
+ public PageChannel getPageChannel() {
+ return getDatabase().getPageChannel();
+ }
+
+ public Column getPrimaryKeyColumn() {
+ return _pkCol;
+ }
+
+ public Column getComplexValueForeignKeyColumn() {
+ return _complexValFkCol;
+ }
+
+ protected List<Column> getTypeColumns() {
+ return _typeCols;
+ }
+
+ public int countValues(int complexValueFk)
+ throws IOException
+ {
+ return getRawValues(complexValueFk,
+ Collections.singleton(_complexValFkCol.getName()))
+ .size();
+ }
+
+ public List<Map<String,Object>> getRawValues(int complexValueFk)
+ throws IOException
+ {
+ return getRawValues(complexValueFk, null);
+ }
+
+ public List<Map<String,Object>> getRawValues(int complexValueFk,
+ Collection<String> columnNames)
+ throws IOException
+ {
+ if(_complexValIdCursor == null) {
+ _complexValIdCursor = new CursorBuilder(_flatTable)
+ .setIndexByColumns(_complexValFkCol)
+ .toIndexCursor();
+ }
+
+ Iterator<Map<String,Object>> entryIter =
+ _complexValIdCursor.entryIterator(columnNames, complexValueFk);
+ if(!entryIter.hasNext()) {
+ return Collections.emptyList();
+ }
+
+ List<Map<String,Object>> values = new ArrayList<Map<String,Object>>();
+ while(entryIter.hasNext()) {
+ values.add(entryIter.next());
+ }
+
+ return values;
+ }
+
+ public List<V> getValues(
+ ComplexValueForeignKey complexValueFk)
+ throws IOException
+ {
+ List<Map<String,Object>> rawValues = getRawValues(complexValueFk.get());
+ if(rawValues.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ return toValues(complexValueFk, rawValues);
+ }
+
+ public int addRawValue(Map<String,Object> rawValue) throws IOException {
+ Object[] row = _flatTable.asRow(rawValue);
+ _flatTable.addRow(row);
+ return (Integer)_pkCol.getRowValue(row);
+ }
+
+ public int addValue(V value) throws IOException {
+ Object[] row = asRow(newRowArray(), value);
+ _flatTable.addRow(row);
+ int id = (Integer)_pkCol.getRowValue(row);
+ value.setId(id);
+ return id;
+ }
+
+ public void addValues(Collection<? extends V> values)
+ throws IOException
+ {
+ for(V value : values) {
+ addValue(value);
+ }
+ }
+
+ public int updateRawValue(Map<String,Object> rawValue) throws IOException {
+ Integer id = (Integer)_pkCol.getRowValue(rawValue);
+ updateRow(id, _flatTable.asUpdateRow(rawValue));
+ return id;
+ }
+
+ public int updateValue(V value) throws IOException {
+ int id = value.getId();
+ updateRow(id, asRow(newRowArray(), value));
+ return id;
+ }
+
+ public void updateValues(Collection<? extends V> values)
+ throws IOException
+ {
+ for(V value : values) {
+ updateValue(value);
+ }
+ }
+
+ private void updateRow(Integer id, Object[] row) throws IOException {
+ if(_pkCursor == null) {
+ _pkCursor = new CursorBuilder(_flatTable)
+ .setIndexByColumns(_pkCol)
+ .toIndexCursor();
+ }
+
+ if(!_pkCursor.findRowByEntry(id)) {
+ throw new IllegalArgumentException("Row with id " + id +
+ " does not exist");
+ }
+
+ _pkCursor.updateCurrentRow(row);
+ }
+
+ protected Object[] asRow(Object[] row, V value) {
+ int id = value.getId();
+ _pkCol.setRowValue(row, ((id != INVALID_ID) ? id : Column.AUTO_NUMBER));
+ int cId = value.getComplexValueForeignKey().get();
+ _complexValFkCol.setRowValue(
+ row, ((cId != INVALID_ID) ? cId : Column.AUTO_NUMBER));
+ return row;
+ }
+
+ private Object[] newRowArray() {
+ return new Object[_flatTable.getColumnCount()];
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder rtn = new StringBuilder();
+ rtn.append("\n\t\tComplexType: " + getType());
+ rtn.append("\n\t\tComplexTypeId: " + _complexTypeId);
+ return rtn.toString();
+ }
+
+ protected static void diffFlatColumns(Table typeObjTable, Table flatTable,
+ List<Column> typeCols,
+ List<Column> otherCols)
+ {
+ // each "flat"" table has the columns from the "type" table, plus some
+ // others. separate the "flat" columns into these 2 buckets
+ for(Column col : flatTable.getColumns()) {
+ boolean found = false;
+ try {
+ typeObjTable.getColumn(col.getName());
+ found = true;
+ } catch(IllegalArgumentException e) {
+ // FIXME better way to test this?
+ }
+ if(found) {
+ typeCols.add(col);
+ } else {
+ otherCols.add(col);
+ }
+ }
+ }
+
+ public abstract ComplexDataType getType();
+
+ protected abstract List<V> toValues(
+ ComplexValueForeignKey complexValueFk,
+ List<Map<String,Object>> rawValues)
+ throws IOException;
+
+ protected static class ComplexValueImpl implements ComplexValue
+ {
+ private int _id;
+ private ComplexValueForeignKey _complexValueFk;
+
+ protected ComplexValueImpl(int id, ComplexValueForeignKey complexValueFk) {
+ _id = id;
+ _complexValueFk = complexValueFk;
+ }
+
+ public int getId() {
+ return _id;
+ }
+
+ public void setId(int id) {
+ if(_id != INVALID_ID) {
+ throw new IllegalStateException("id may not be reset");
+ }
+ _id = id;
+ }
+
+ public ComplexValueForeignKey getComplexValueForeignKey() {
+ return _complexValueFk;
+ }
+
+ public void setComplexValueForeignKey(ComplexValueForeignKey complexValueFk)
+ {
+ if(_complexValueFk != INVALID_COMPLEX_VALUE_ID) {
+ throw new IllegalStateException("complexValueFk may not be reset");
+ }
+ _complexValueFk = complexValueFk;
+ }
+
+ public Column getColumn() {
+ return _complexValueFk.getColumn();
+ }
+
+ public void update() throws IOException {
+ throw new UnsupportedOperationException(
+ "This column does not support value updates");
+ }
+
+ @Override
+ public int hashCode() {
+ return ((_id * 37) ^ _complexValueFk.hashCode());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return ((this == o) ||
+ ((o != null) && (getClass() == o.getClass()) &&
+ (_id == ((ComplexValueImpl)o)._id) &&
+ _complexValueFk.equals(((ComplexValueImpl)o)._complexValueFk)));
+ }
+ }
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/ComplexDataType.java b/src/java/com/healthmarketscience/jackcess/complex/ComplexDataType.java
new file mode 100644
index 0000000..1c5e699
--- /dev/null
+++ b/src/java/com/healthmarketscience/jackcess/complex/ComplexDataType.java
@@ -0,0 +1,29 @@
+/*
+Copyright (c) 2011 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.complex;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public enum ComplexDataType
+{
+ ATTACHMENT, MULTI_VALUE, VERSION_HISTORY, UNSUPPORTED;
+}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/ComplexValue.java b/src/java/com/healthmarketscience/jackcess/complex/ComplexValue.java
new file mode 100644
index 0000000..e65a09c
--- /dev/null
+++ b/src/java/com/healthmarketscience/jackcess/complex/ComplexValue.java
@@ -0,0 +1,43 @@
+/*
+Copyright (c) 2011 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.complex;
+
+import java.io.IOException;
+
+import com.healthmarketscience.jackcess.Column;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public interface ComplexValue
+{
+ public int getId();
+
+ public void setId(int newId);
+
+ public ComplexValueForeignKey getComplexValueForeignKey();
+
+ public void setComplexValueForeignKey(ComplexValueForeignKey complexValueFk);
+
+ public Column getColumn();
+
+ public void update() throws IOException;
+}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java b/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java
new file mode 100644
index 0000000..294958f
--- /dev/null
+++ b/src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java
@@ -0,0 +1,246 @@
+/*
+Copyright (c) 2011 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.complex;
+
+import java.io.IOException;
+import java.io.ObjectStreamException;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import com.healthmarketscience.jackcess.Column;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class ComplexValueForeignKey extends Number
+{
+ private static final long serialVersionUID = 20110805L;
+
+ private final Column _column;
+ private final int _value;
+ private List<? extends ComplexValue> _values;
+
+ public ComplexValueForeignKey(Column column, int value)
+ {
+ _column = column;
+ _value = value;
+ }
+
+ public int get() {
+ return _value;
+ }
+
+ public Column getColumn() {
+ return _column;
+ }
+
+ @Override
+ public byte byteValue() {
+ return (byte)get();
+ }
+
+ @Override
+ public short shortValue() {
+ return (short)get();
+ }
+
+ @Override
+ public int intValue() {
+ return get();
+ }
+
+ @Override
+ public long longValue() {
+ return get();
+ }
+
+ @Override
+ public float floatValue() {
+ return get();
+ }
+
+ @Override
+ public double doubleValue() {
+ return get();
+ }
+
+ public ComplexDataType getComplexType() {
+ return getComplexInfo().getType();
+ }
+
+ protected ComplexColumnInfo<? extends ComplexValue> getComplexInfo() {
+ return _column.getComplexInfo();
+ }
+
+ protected VersionHistoryColumnInfo getVersionInfo() {
+ return (VersionHistoryColumnInfo)getComplexInfo();
+ }
+
+ protected AttachmentColumnInfo getAttachmentInfo() {
+ return (AttachmentColumnInfo)getComplexInfo();
+ }
+
+ protected MultiValueColumnInfo getMultiValueInfo() {
+ return (MultiValueColumnInfo)getComplexInfo();
+ }
+
+ public int countValues()
+ throws IOException
+ {
+ return getComplexInfo().countValues(get());
+ }
+
+ public List<Map<String,Object>> getRawValues()
+ throws IOException
+ {
+ return getComplexInfo().getRawValues(get());
+ }
+
+ public List<? extends ComplexValue> getValues()
+ throws IOException
+ {
+ if(_values == null) {
+ _values = getComplexInfo().getValues(this);
+ }
+ return _values;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<Version> getVersions()
+ throws IOException
+ {
+ if(getComplexType() != ComplexDataType.VERSION_HISTORY) {
+ throw new UnsupportedOperationException();
+ }
+ return (List<Version>)getValues();
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<Attachment> getAttachments()
+ throws IOException
+ {
+ if(getComplexType() != ComplexDataType.ATTACHMENT) {
+ throw new UnsupportedOperationException();
+ }
+ return (List<Attachment>)getValues();
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<SingleValue> getMultiValues()
+ throws IOException
+ {
+ if(getComplexType() != ComplexDataType.MULTI_VALUE) {
+ throw new UnsupportedOperationException();
+ }
+ return (List<SingleValue>)getValues();
+ }
+
+ public void reset() {
+ // discard any cached values
+ _values = null;
+ }
+
+ public Version addVersion(String value)
+ throws IOException
+ {
+ return addVersion(value, new Date());
+ }
+
+ public Version addVersion(String value, Date modifiedDate)
+ throws IOException
+ {
+ reset();
+ Version v = VersionHistoryColumnInfo.newVersion(this, value, modifiedDate);
+ getVersionInfo().addValue(v);
+ return v;
+ }
+
+ public Attachment addAttachment(byte[] data)
+ throws IOException
+ {
+ return addAttachment(null, null, null, data, null, null);
+ }
+
+ public Attachment addAttachment(
+ String url, String name, String type, byte[] data,
+ Date timeStamp, Integer flags)
+ throws IOException
+ {
+ reset();
+ Attachment a = AttachmentColumnInfo.newAttachment(
+ this, url, name, type, data, timeStamp, flags);
+ getAttachmentInfo().addValue(a);
+ return a;
+ }
+
+ public Attachment updateAttachment(Attachment attachment)
+ throws IOException
+ {
+ reset();
+ getAttachmentInfo().updateValue(attachment);
+ return attachment;
+ }
+
+ public SingleValue addMultiValue(Object value)
+ throws IOException
+ {
+ reset();
+ SingleValue v = MultiValueColumnInfo.newSingleValue(this, value);
+ getMultiValueInfo().addValue(v);
+ return v;
+ }
+
+ public SingleValue updateMultiValue(SingleValue value)
+ throws IOException
+ {
+ reset();
+ getMultiValueInfo().updateValue(value);
+ return value;
+ }
+
+ private Object writeReplace() throws ObjectStreamException {
+ // if we are going to serialize this ComplexValueForeignKey, convert it
+ // back to a normal Integer (in case it is restored outside of the context
+ // of jackcess)
+ return Integer.valueOf(_value);
+ }
+
+ @Override
+ public int hashCode() {
+ return _value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return ((this == o) ||
+ ((o != null) && (getClass() == o.getClass()) &&
+ (_value == ((ComplexValueForeignKey)o)._value) &&
+ (_column == ((ComplexValueForeignKey)o)._column)));
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.valueOf(_value);
+ }
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/MultiValueColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/MultiValueColumnInfo.java
new file mode 100644
index 0000000..1b117c6
--- /dev/null
+++ b/src/java/com/healthmarketscience/jackcess/complex/MultiValueColumnInfo.java
@@ -0,0 +1,143 @@
+/*
+Copyright (c) 2011 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.complex;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.DataType;
+import com.healthmarketscience.jackcess.Table;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class MultiValueColumnInfo extends ComplexColumnInfo<SingleValue>
+{
+ private static final Set<DataType> VALUE_TYPES = EnumSet.of(
+ DataType.BYTE, DataType.INT, DataType.LONG, DataType.FLOAT,
+ DataType.DOUBLE, DataType.GUID, DataType.NUMERIC, DataType.TEXT);
+
+ private final Column _valueCol;
+
+ public MultiValueColumnInfo(Column column, int complexId,
+ Table typeObjTable, Table flatTable)
+ throws IOException
+ {
+ super(column, complexId, typeObjTable, flatTable);
+
+ _valueCol = getTypeColumns().get(0);
+ }
+
+ @Override
+ public ComplexDataType getType()
+ {
+ return ComplexDataType.MULTI_VALUE;
+ }
+
+ public Column getValueColumn() {
+ return _valueCol;
+ }
+
+ @Override
+ protected List<SingleValue> toValues(ComplexValueForeignKey complexValueFk,
+ List<Map<String,Object>> rawValues)
+ throws IOException
+ {
+ List<SingleValue> values = new ArrayList<SingleValue>();
+ for(Map<String,Object> rawValue : rawValues) {
+ values.add(toSingleValue(complexValueFk, rawValue));
+ }
+
+ return values;
+ }
+
+ protected SingleValueImpl toSingleValue(
+ ComplexValueForeignKey complexValueFk,
+ Map<String,Object> rawValue)
+ {
+ int id = (Integer)getPrimaryKeyColumn().getRowValue(rawValue);
+ Object value = getValueColumn().getRowValue(rawValue);
+
+ return new SingleValueImpl(id, complexValueFk, value);
+ }
+
+ @Override
+ protected Object[] asRow(Object[] row, SingleValue value) {
+ super.asRow(row, value);
+ getValueColumn().setRowValue(row, value.get());
+ return row;
+ }
+
+ public static SingleValue newSingleValue(Object value) {
+ return newSingleValue(INVALID_COMPLEX_VALUE_ID, value);
+ }
+
+ public static SingleValue newSingleValue(
+ ComplexValueForeignKey complexValueFk, Object value) {
+ return new SingleValueImpl(INVALID_ID, complexValueFk, value);
+ }
+
+ public static boolean isMultiValueColumn(Table typeObjTable) {
+ // if we found a single value of a "simple" type, then we are dealing with
+ // a multi-value column
+ List<Column> typeCols = typeObjTable.getColumns();
+ return ((typeCols.size() == 1) &&
+ VALUE_TYPES.contains(typeCols.get(0).getType()));
+ }
+
+ private static class SingleValueImpl extends ComplexValueImpl
+ implements SingleValue
+ {
+ private Object _value;
+
+ private SingleValueImpl(int id, ComplexValueForeignKey complexValueFk,
+ Object value)
+ {
+ super(id, complexValueFk);
+ _value = value;
+ }
+
+ public Object get() {
+ return _value;
+ }
+
+ public void set(Object value) {
+ _value = value;
+ }
+
+ @Override
+ public void update() throws IOException {
+ getComplexValueForeignKey().updateMultiValue(this);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "SingleValue(" + getComplexValueForeignKey() + "," + getId() +
+ ") " + get();
+ }
+ }
+}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/SingleValue.java b/src/java/com/healthmarketscience/jackcess/complex/SingleValue.java
new file mode 100644
index 0000000..0859460
--- /dev/null
+++ b/src/java/com/healthmarketscience/jackcess/complex/SingleValue.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2011 Boomi, Inc.
+
+package com.healthmarketscience.jackcess.complex;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public interface SingleValue extends ComplexValue
+{
+ public Object get();
+
+ public void set(Object value);
+}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java
new file mode 100644
index 0000000..fd2a0fc
--- /dev/null
+++ b/src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java
@@ -0,0 +1,63 @@
+/*
+Copyright (c) 2011 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.complex;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.Table;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class UnsupportedColumnInfo extends ComplexColumnInfo<ComplexValue>
+{
+
+ public UnsupportedColumnInfo(Column column, int complexId, Table typeObjTable,
+ Table flatTable)
+ throws IOException
+ {
+ super(column, complexId, typeObjTable, flatTable);
+ }
+
+ @Override
+ public ComplexDataType getType()
+ {
+ return ComplexDataType.UNSUPPORTED;
+ }
+
+ @Override
+ protected List<ComplexValue> toValues(ComplexValueForeignKey complexValueFk,
+ List<Map<String,Object>> rawValues)
+ throws IOException
+ {
+ // FIXME
+ return null;
+ }
+
+ public ComplexValue newValue() {
+ // FIXME
+ return null;
+ }
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/Version.java b/src/java/com/healthmarketscience/jackcess/complex/Version.java
new file mode 100644
index 0000000..70477f8
--- /dev/null
+++ b/src/java/com/healthmarketscience/jackcess/complex/Version.java
@@ -0,0 +1,33 @@
+/*
+Copyright (c) 2011 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.complex;
+
+import java.util.Date;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public interface Version extends ComplexValue, Comparable<Version>
+{
+ public String getValue();
+
+ public Date getModifiedDate();
+}
diff --git a/src/java/com/healthmarketscience/jackcess/complex/VersionHistoryColumnInfo.java b/src/java/com/healthmarketscience/jackcess/complex/VersionHistoryColumnInfo.java
new file mode 100644
index 0000000..0cf2fe7
--- /dev/null
+++ b/src/java/com/healthmarketscience/jackcess/complex/VersionHistoryColumnInfo.java
@@ -0,0 +1,212 @@
+/*
+Copyright (c) 2011 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.complex;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.Table;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class VersionHistoryColumnInfo extends ComplexColumnInfo<Version>
+{
+ private final Column _valueCol;
+ private final Column _modifiedCol;
+
+ public VersionHistoryColumnInfo(Column column, int complexId,
+ Table typeObjTable, Table flatTable)
+ throws IOException
+ {
+ super(column, complexId, typeObjTable, flatTable);
+
+ Column valueCol = null;
+ Column modifiedCol = null;
+ for(Column col : getTypeColumns()) {
+ switch(col.getType()) {
+ case SHORT_DATE_TIME:
+ modifiedCol = col;
+ break;
+ case MEMO:
+ valueCol = col;
+ break;
+ default:
+ // ignore
+ }
+ }
+
+ _valueCol = valueCol;
+ _modifiedCol = modifiedCol;
+ }
+
+ @Override
+ public void postTableLoadInit() throws IOException {
+ super.postTableLoadInit();
+
+ // link up with the actual versioned column. it should have the same name
+ // as the "value" column in the type table.
+ Column versionedCol = getColumn().getTable().getColumn(
+ getValueColumn().getName());
+ versionedCol.setVersionHistoryColumn(getColumn());
+ }
+
+ public Column getValueColumn() {
+ return _valueCol;
+ }
+
+ public Column getModifiedDateColumn() {
+ return _modifiedCol;
+ }
+
+ @Override
+ public ComplexDataType getType() {
+ return ComplexDataType.VERSION_HISTORY;
+ }
+
+ @Override
+ protected List<Version> toValues(ComplexValueForeignKey complexValueFk,
+ List<Map<String,Object>> rawValues)
+ throws IOException
+ {
+ List<Version> versions = new ArrayList<Version>();
+ for(Map<String,Object> rawValue : rawValues) {
+ versions.add(toVersion(complexValueFk, rawValue));
+ }
+
+ // order versions newest to oldest
+ Collections.sort(versions);
+
+ return versions;
+ }
+
+ protected VersionImpl toVersion(ComplexValueForeignKey complexValueFk,
+ Map<String,Object> rawValue) {
+ int id = (Integer)getPrimaryKeyColumn().getRowValue(rawValue);
+ String value = (String)getValueColumn().getRowValue(rawValue);
+ Date modifiedDate = (Date)getModifiedDateColumn().getRowValue(rawValue);
+
+ return new VersionImpl(id, complexValueFk, value, modifiedDate);
+ }
+
+ @Override
+ protected Object[] asRow(Object[] row, Version version) {
+ super.asRow(row, version);
+ getValueColumn().setRowValue(row, version.getValue());
+ getModifiedDateColumn().setRowValue(row, version.getModifiedDate());
+ return row;
+ }
+
+ public static Version newVersion(String value, Date modifiedDate) {
+ return newVersion(INVALID_COMPLEX_VALUE_ID, value, modifiedDate);
+ }
+
+ public static Version newVersion(ComplexValueForeignKey complexValueFk,
+ String value, Date modifiedDate) {
+ return new VersionImpl(INVALID_ID, complexValueFk, value, modifiedDate);
+ }
+
+ public static boolean isVersionHistoryColumn(Table typeObjTable) {
+ // version history data has these columns <value>(MEMO),
+ // <modified>(SHORT_DATE_TIME)
+ List<Column> typeCols = typeObjTable.getColumns();
+ if(typeCols.size() < 2) {
+ return false;
+ }
+
+ int numMemo = 0;
+ int numDate = 0;
+
+ for(Column col : typeCols) {
+ switch(col.getType()) {
+ case SHORT_DATE_TIME:
+ ++numDate;
+ break;
+ case MEMO:
+ ++numMemo;
+ break;
+ default:
+ // ignore
+ }
+ }
+
+ // be flexible, allow for extra columns...
+ return((numMemo >= 1) && (numDate >= 1));
+ }
+
+ private static class VersionImpl extends ComplexValueImpl implements Version
+ {
+ private final String _value;
+ private final Date _modifiedDate;
+
+ private VersionImpl(int id, ComplexValueForeignKey complexValueFk,
+ String value, Date modifiedDate)
+ {
+ super(id, complexValueFk);
+ _value = value;
+ _modifiedDate = modifiedDate;
+ }
+
+ public String getValue() {
+ return _value;
+ }
+
+ public Date getModifiedDate() {
+ return _modifiedDate;
+ }
+
+ public int compareTo(Version o) {
+ Date d1 = getModifiedDate();
+ Date d2 = o.getModifiedDate();
+
+ // sort by descending date (newest/greatest first)
+ int cmp = d2.compareTo(d1);
+ if(cmp != 0) {
+ return cmp;
+ }
+
+ // use id, then complexValueFk to break ties (although we really
+ // shouldn't be comparing across different columns)
+ int id1 = getId();
+ int id2 = o.getId();
+ if(id1 != id2) {
+ return ((id1 > id2) ? -1 : 1);
+ }
+ id1 = getComplexValueForeignKey().get();
+ id2 = o.getComplexValueForeignKey().get();
+ return ((id1 > id2) ? -1 :
+ ((id1 < id2) ? 1 : 0));
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Version(" + getComplexValueForeignKey() + "," + getId() + ") " +
+ getModifiedDate() + ", " + getValue();
+ }
+ }
+
+}