diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2011-10-26 02:45:20 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2011-10-26 02:45:20 +0000 |
commit | b014c0d170abc6bdca38078aaf1897aa66125152 (patch) | |
tree | ae88feddf4083861ba41314b732c4cb352a1510d /src/java/com/healthmarketscience/jackcess/complex | |
parent | c2c8d0a7be74b1baacd97ce430225270c8208123 (diff) | |
download | jackcess-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')
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(); + } + } + +} |