]> source.dussan.org Git - jackcess.git/commitdiff
move complex type impls to impl
authorJames Ahlborn <jtahlborn@yahoo.com>
Thu, 21 Mar 2013 01:36:28 +0000 (01:36 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Thu, 21 Mar 2013 01:36:28 +0000 (01:36 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/jackcess-2@699 f203690c-595d-4dc9-a70b-905162fa7fd2

15 files changed:
src/java/com/healthmarketscience/jackcess/complex/AttachmentColumnInfo.java
src/java/com/healthmarketscience/jackcess/complex/ComplexColumnInfo.java
src/java/com/healthmarketscience/jackcess/complex/ComplexValue.java
src/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java
src/java/com/healthmarketscience/jackcess/complex/MultiValueColumnInfo.java
src/java/com/healthmarketscience/jackcess/complex/UnsupportedColumnInfo.java
src/java/com/healthmarketscience/jackcess/complex/VersionHistoryColumnInfo.java
src/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
src/java/com/healthmarketscience/jackcess/impl/ComplexColumnSupport.java
src/java/com/healthmarketscience/jackcess/impl/complex/AttachmentColumnInfoImpl.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/impl/complex/ComplexColumnInfoImpl.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/impl/complex/ComplexValueForeignKeyImpl.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnInfoImpl.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/impl/complex/UnsupportedColumnInfoImpl.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/impl/complex/VersionHistoryColumnInfoImpl.java [new file with mode: 0644]

index f715d7c6d5acbfebbaf4ac1afd3f565f5b78a7a2..f2f605abc76a46e5f0a1ba3de0556386b4cb71c7 100644 (file)
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2011 James Ahlborn
+Copyright (c) 2013 James Ahlborn
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
@@ -19,256 +19,12 @@ USA
 
 package com.healthmarketscience.jackcess.complex;
 
-import java.io.IOException;
-import java.util.Date;
-
-import com.healthmarketscience.jackcess.Column;
-import com.healthmarketscience.jackcess.Row;
-import com.healthmarketscience.jackcess.Table;
-import com.healthmarketscience.jackcess.impl.ByteUtil;
-
-
 /**
  * Complex column info for a column holding 0 or more attachments per row.
  *
  * @author James Ahlborn
  */
-public class AttachmentColumnInfo extends ComplexColumnInfo<Attachment>
+public interface 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 AttachmentImpl toValue(ComplexValueForeignKey complexValueFk,
-                                   Row rawValue) {
-    ComplexValue.Id id = getValueId(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_FK, 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_FK, 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);
-  }
-
-  
-  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(Id 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;
-    }  
 
-    public void update() throws IOException {
-      getComplexValueForeignKey().updateAttachment(this);
-    }
-    
-    public void delete() throws IOException {
-      getComplexValueForeignKey().deleteAttachment(this);
-    }
-    
-    @Override
-    public String toString()
-    {
-      return "Attachment(" + getComplexValueForeignKey() + "," + getId() +
-        ") " + getFileUrl() + ", " + getFileName() + ", " + getFileType()
-        + ", " + getFileTimeStamp() + ", " + getFileFlags()  + ", " +
-        ByteUtil.toHexString(getFileData());
-    } 
-  }
-  
 }
index 3b9ab66e034795d0613395a2e491857452e1f126..14851f6838666e36735ff9169d0f379855bfc24a 100644 (file)
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2011 James Ahlborn
+Copyright (c) 2013 James Ahlborn
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
@@ -20,441 +20,55 @@ USA
 package com.healthmarketscience.jackcess.complex;
 
 import java.io.IOException;
-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.Row;
-import com.healthmarketscience.jackcess.RowId;
-import com.healthmarketscience.jackcess.RuntimeIOException;
-import com.healthmarketscience.jackcess.Table;
-import com.healthmarketscience.jackcess.impl.ColumnImpl;
-import com.healthmarketscience.jackcess.impl.TableImpl;
 
 /**
  * Base class for the additional information tracked for complex columns.
  *
  * @author James Ahlborn
  */
-public abstract class ComplexColumnInfo<V extends ComplexValue>
+public interface ComplexColumnInfo<V extends ComplexValue>
 {
-  private static final int INVALID_ID_VALUE = -1;
-  public static final ComplexValue.Id INVALID_ID = new ComplexValueIdImpl(
-      INVALID_ID_VALUE, null);
-  public static final ComplexValueForeignKey INVALID_FK =
-    new ComplexValueForeignKey(null, INVALID_ID_VALUE);
+  public ComplexDataType getType();
 
-  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 _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.
-    List<Column> typeCols = new ArrayList<Column>();
-    List<Column> otherCols = new ArrayList<Column>();
-    diffFlatColumns(typeObjTable, flatTable, typeCols, otherCols);
+  public int countValues(int complexValueFk) throws IOException;
 
-    _typeCols = Collections.unmodifiableList(typeCols);
-
-    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 void postTableLoadInit() throws IOException {
-    // nothing to do in base class
-  }
-  
-  public Column getColumn() {
-    return _column;
-  }
-
-  public Database getDatabase() {
-    return getColumn().getDatabase();
-  }
-
-  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<Row> getRawValues(int complexValueFk)
-    throws IOException
-  {
-    return getRawValues(complexValueFk, null);
-  }
-
-  private Iterator<Row> getComplexValFkIter(
-      int complexValueFk, Collection<String> columnNames)
-    throws IOException
-  {
-    if(_complexValIdCursor == null) {
-      _complexValIdCursor = new CursorBuilder(_flatTable)
-        .setIndexByColumns(_complexValFkCol)
-        .toIndexCursor();
-    }
+    throws IOException;
 
-    return _complexValIdCursor.entryIterator(columnNames, complexValueFk);
-  }
-  
   public List<Row> getRawValues(int complexValueFk,
                                 Collection<String> columnNames)
-    throws IOException
-  {
-    Iterator<Row> entryIter =
-      getComplexValFkIter(complexValueFk, columnNames);
-    if(!entryIter.hasNext()) {
-      return Collections.emptyList();
-    }
-
-    List<Row> values = new ArrayList<Row>();
-    while(entryIter.hasNext()) {
-      values.add(entryIter.next());
-    }
-    
-    return values;
-  }
+    throws IOException;
 
   public List<V> getValues(ComplexValueForeignKey complexValueFk)
-    throws IOException
-  {
-    List<Row> rawValues = getRawValues(complexValueFk.get());
-    if(rawValues.isEmpty()) {
-      return Collections.emptyList();
-    }
-
-    return toValues(complexValueFk, rawValues);
-  }
-  
-  protected List<V> toValues(ComplexValueForeignKey complexValueFk,
-                             List<Row> rawValues)
-    throws IOException
-  {
-    List<V> values = new ArrayList<V>();
-    for(Row rawValue : rawValues) {
-      values.add(toValue(complexValueFk, rawValue));
-    }
-
-    return values;
-  }
+    throws IOException;
 
   public ComplexValue.Id addRawValue(Map<String,?> rawValue)
-    throws IOException 
-  {
-    Object[] row = ((TableImpl)_flatTable).asRowWithRowId(rawValue);
-    _flatTable.addRow(row);
-    return getValueId(row);
-  }
-
-  public ComplexValue.Id addValue(V value) throws IOException {
-    Object[] row = asRow(newRowArray(), value);
-    _flatTable.addRow(row);
-    ComplexValue.Id id = getValueId(row);
-    value.setId(id);
-    return id;
-  }
-
-  public void addValues(Collection<? extends V> values) throws IOException {
-    for(V value : values) {
-      addValue(value);
-    }
-  }
-
-  public ComplexValue.Id updateRawValue(Row rawValue) throws IOException {
-    _flatTable.updateRow(rawValue);
-    return getValueId(rawValue);
-  }
-  
-  public ComplexValue.Id updateValue(V value) throws IOException {
-    ComplexValue.Id 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);
-    }
-  }
-
-  public void deleteRawValue(Row rawValue) throws IOException {
-    deleteRow(rawValue.getId());
-  }
-  
-  public void deleteValue(V value) throws IOException {
-    deleteRow(value.getId().getRowId());
-  }
-
-  public void deleteValues(Collection<? extends V> values) throws IOException {
-    for(V value : values) {
-      deleteValue(value);
-    }
-  }
-
-  public void deleteAllValues(int complexValueFk) throws IOException {
-    Iterator<Row> entryIter =
-      getComplexValFkIter(complexValueFk, Collections.<String>emptySet());
-    try {
-      while(entryIter.hasNext()) {
-        entryIter.next();
-        entryIter.remove();
-      }
-    } catch(RuntimeIOException e) {
-      throw (IOException)e.getCause();
-    }
-  }
-
-  public void deleteAllValues(ComplexValueForeignKey complexValueFk)
-    throws IOException
-  {
-    deleteAllValues(complexValueFk.get());
-  }
-
-  private void updateRow(ComplexValue.Id id, Object[] row) throws IOException {
-    ((TableImpl)_flatTable).updateRow(id.getRowId(), row);
-  }
-  
-  private void deleteRow(RowId rowId) throws IOException {
-    ((TableImpl)_flatTable).deleteRow(rowId);
-  }
-  
-  protected ComplexValueIdImpl getValueId(Row row) {
-    int idVal = (Integer)getPrimaryKeyColumn().getRowValue(row);
-    return new ComplexValueIdImpl(idVal, row.getId());
-  }
-
-  protected ComplexValueIdImpl getValueId(Object[] row) {
-    int idVal = (Integer)getPrimaryKeyColumn().getRowValue(row);
-    return new ComplexValueIdImpl(idVal, 
-                                  ((TableImpl)_flatTable).getRowId(row));
-  }
-
-  protected Object[] asRow(Object[] row, V value) {
-    ComplexValue.Id id = value.getId();
-    _pkCol.setRowValue(
-        row, ((id != INVALID_ID) ? id : Column.AUTO_NUMBER));
-    ComplexValueForeignKey cFk = value.getComplexValueForeignKey();
-    _complexValFkCol.setRowValue(
-        row, ((cFk != INVALID_FK) ? cFk : Column.AUTO_NUMBER));
-    return row;
-  }
-
-  private Object[] newRowArray() {
-    Object[] row = new Object[_flatTable.getColumnCount() + 1];
-    row[row.length - 1] = ColumnImpl.RETURN_ROW_ID;
-    return row;
-  }
-  
-  @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()) {
-      if(((TableImpl)typeObjTable).hasColumn(col.getName())) {
-        typeCols.add(col);
-      } else {
-        otherCols.add(col);
-      }  
-    } 
-  }
-  
-  public abstract ComplexDataType getType();
-
-  protected abstract V toValue(
-      ComplexValueForeignKey complexValueFk,
-      Row rawValues)
     throws IOException;
-  
-  protected static abstract class ComplexValueImpl implements ComplexValue
-  {
-    private Id _id;
-    private ComplexValueForeignKey _complexValueFk;
 
-    protected ComplexValueImpl(Id id, ComplexValueForeignKey complexValueFk) {
-      _id = id;
-      _complexValueFk = complexValueFk;
-    }
+  public ComplexValue.Id addValue(V value) throws IOException;
 
-    public Id getId() {
-      return _id;
-    }
+  public void addValues(Collection<? extends V> values) throws IOException;
 
-    public void setId(Id id) {
-      if(_id == id) {
-        // harmless, ignore
-        return;
-      }
-      if(_id != INVALID_ID) {
-        throw new IllegalStateException("id may not be reset");
-      }
-      _id = id;
-    }
-    
-    public ComplexValueForeignKey getComplexValueForeignKey() {
-      return _complexValueFk;
-    }
+  public ComplexValue.Id updateRawValue(Row rawValue) throws IOException;
 
-    public void setComplexValueForeignKey(ComplexValueForeignKey complexValueFk)
-    {
-      if(_complexValueFk == complexValueFk) {
-        // harmless, ignore
-        return;
-      }
-      if(_complexValueFk != INVALID_FK) {
-        throw new IllegalStateException("complexValueFk may not be reset");
-      }
-      _complexValueFk = complexValueFk;
-    }
+  public ComplexValue.Id updateValue(V value) throws IOException;
 
-    public Column getColumn() {
-      return _complexValueFk.getColumn();
-    }
-    
-    @Override
-    public int hashCode() {
-      return ((_id.get() * 37) ^ _complexValueFk.hashCode());
-    }
+  public void updateValues(Collection<? extends V> values) throws IOException;
 
-    @Override
-    public boolean equals(Object o) {
-      return ((this == o) ||
-              ((o != null) && (getClass() == o.getClass()) &&
-               (_id == ((ComplexValueImpl)o)._id) &&
-               _complexValueFk.equals(((ComplexValueImpl)o)._complexValueFk)));
-    }
-  }
+  public void deleteRawValue(Row rawValue) throws IOException;
 
-  /**
-   * Implementation of ComplexValue.Id.
-   */
-  private static final class ComplexValueIdImpl extends ComplexValue.Id
-  {
-    private static final long serialVersionUID = 20130318L;    
+  public void deleteValue(V value) throws IOException;
 
-    private final int _value;
-    private final RowId _rowId;
+  public void deleteValues(Collection<? extends V> values) throws IOException;
 
-    protected ComplexValueIdImpl(int value, RowId rowId) {
-      _value = value;
-      _rowId = rowId;
-    }
-    
-    @Override
-    public int get() {
-      return _value;
-    }
+  public void deleteAllValues(int complexValueFk) throws IOException;
 
-    @Override
-    public RowId getRowId() {
-      return _rowId;
-    }
-  
-    @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();
-    }
-    
-    @Override
-    public int hashCode() {
-      return _value;
-    }
-  
-    @Override
-    public boolean equals(Object o) {
-      return ((this == o) ||
-              ((o != null) && (getClass() == o.getClass()) &&
-               (_value == ((ComplexValueIdImpl)o)._value)));
-    }
+  public void deleteAllValues(ComplexValueForeignKey complexValueFk)
+    throws IOException;
 
-    @Override
-    public String toString()
-    {
-      return String.valueOf(_value);
-    }  
-  }
-  
 }
index 4513f07102a9dc17a2fdc6083328b44ba25a462b..cd7755480c7d28d70b4b38f0d14c27928931bec6 100644 (file)
@@ -20,6 +20,7 @@ USA
 package com.healthmarketscience.jackcess.complex;
 
 import java.io.IOException;
+import java.io.ObjectStreamException;
 
 import com.healthmarketscience.jackcess.Column;
 import com.healthmarketscience.jackcess.RowId;
@@ -83,6 +84,60 @@ public interface ComplexValue
   {
     private static final long serialVersionUID = 20130318L;    
 
+    @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();
+    }
+    
+    @Override
+    public int hashCode() {
+      return get();
+    }
+  
+    @Override
+    public boolean equals(Object o) {
+      return ((this == o) ||
+              ((o != null) && (getClass() == o.getClass()) &&
+               (get() == ((Id)o).get())));
+    }
+
+    @Override
+    public String toString() {
+      return String.valueOf(get());
+    }  
+
+    protected final Object writeReplace() throws ObjectStreamException {
+      // if we are going to serialize this ComplexValue.Id, convert it back to
+      // a normal Integer (in case it is restored outside of the context of
+      // jackcess)
+      return Integer.valueOf(get());
+    }
+
     /**
      * Returns the unique identifier of this complex value (this value is unique
      * among all values in all rows of the main table for the complex column).
index a4b4d0213b1953f546333e08d0e3080b2b0390b1..1b651cd99cc5f914294bb49a78875d7c6c38f160 100644 (file)
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2011 James Ahlborn
+Copyright (c) 2013 James Ahlborn
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
@@ -24,9 +24,8 @@ import java.io.ObjectStreamException;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
-
 import com.healthmarketscience.jackcess.Column;
-import com.healthmarketscience.jackcess.Row;
+
 
 /**
  * Value which is returned for a complex column.  This value corresponds to a
@@ -42,27 +41,10 @@ import com.healthmarketscience.jackcess.Row;
  *
  * @author James Ahlborn
  */
-public class ComplexValueForeignKey extends Number
+public abstract class ComplexValueForeignKey extends Number
 {
-  private static final long serialVersionUID = 20110805L;  
-  
-  private transient final Column _column;
-  private final int _value;
-  private transient List<? extends ComplexValue> _values;
-  
-  public ComplexValueForeignKey(Column column, int value) {
-    _column = column;
-    _value = value;
-  }
-
-  public int get() {
-    return _value;
-  }
+  private static final long serialVersionUID = 20130319L;  
 
-  public Column getColumn() {
-    return _column;
-  }
-  
   @Override
   public byte byteValue() {
     return (byte)get();
@@ -93,225 +75,94 @@ public class ComplexValueForeignKey extends Number
     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();
-  }
-    
-  protected UnsupportedColumnInfo getUnsupportedInfo() {
-    return (UnsupportedColumnInfo)getComplexInfo();
-  }
-    
-  public int countValues()
-    throws IOException
-  {
-    return getComplexInfo().countValues(get());
-  }
-  
-  public List<Row> 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();
-  }
-  
-  @SuppressWarnings("unchecked")
-  public List<UnsupportedValue> getUnsupportedValues()
-    throws IOException
-  {
-    if(getComplexType() != ComplexDataType.UNSUPPORTED) {
-      throw new UnsupportedOperationException();
-    }
-    return (List<UnsupportedValue>)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 Attachment deleteAttachment(Attachment attachment)
-    throws IOException
-  {
-    reset();
-    getAttachmentInfo().deleteValue(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;
-  }
-  
-  public SingleValue deleteMultiValue(SingleValue value)
-    throws IOException
-  {
-    reset();
-    getMultiValueInfo().deleteValue(value);
-    return value;
-  }
-  
-  public UnsupportedValue addUnsupportedValue(Map<String,?> values)
-    throws IOException
-  {
-    reset();
-    UnsupportedValue v = UnsupportedColumnInfo.newValue(this, values);
-    getUnsupportedInfo().addValue(v);
-    return v;
-  }
-  
-  public UnsupportedValue updateUnsupportedValue(UnsupportedValue value)
-    throws IOException
-  {
-    reset();
-    getUnsupportedInfo().updateValue(value);
-    return value;
-  }
-  
-  public UnsupportedValue deleteUnsupportedValue(UnsupportedValue value)
-    throws IOException
-  {
-    reset();
-    getUnsupportedInfo().deleteValue(value);
-    return value;
-  }
-  
-  public void deleteAllValues()
-    throws IOException
-  {
-    reset();
-    getComplexInfo().deleteAllValues(this);
-  }
-  
-  private Object writeReplace() throws ObjectStreamException {
+  protected final 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);
+    return Integer.valueOf(get());
   }
   
   @Override
   public int hashCode() {
-    return _value;
+    return get();
   }
   
   @Override
   public boolean equals(Object o) {
     return ((this == o) ||
             ((o != null) && (getClass() == o.getClass()) &&
-             (_value == ((ComplexValueForeignKey)o)._value) &&
-             (_column == ((ComplexValueForeignKey)o)._column)));
+             (get() == ((ComplexValueForeignKey)o).get())));
   }
 
   @Override
   public String toString()
   {
-    return String.valueOf(_value);
+    return String.valueOf(get());
   }
   
+
+  public abstract int get();
+
+  public abstract Column getColumn();
+
+  public abstract ComplexDataType getComplexType();
+
+  public abstract int countValues() throws IOException;
+
+  public abstract List<? extends ComplexValue> getValues() throws IOException;
+
+  public abstract List<Version> getVersions() throws IOException;
+
+  public abstract List<Attachment> getAttachments()
+    throws IOException;
+
+  public abstract List<SingleValue> getMultiValues()
+    throws IOException;
+
+  public abstract List<UnsupportedValue> getUnsupportedValues()
+    throws IOException;
+
+  public abstract void reset();
+
+  public abstract Version addVersion(String value)
+    throws IOException;
+
+  public abstract Version addVersion(String value, Date modifiedDate)
+    throws IOException;
+
+  public abstract Attachment addAttachment(byte[] data)
+    throws IOException;
+
+  public abstract Attachment addAttachment(
+      String url, String name, String type, byte[] data,
+      Date timeStamp, Integer flags)
+    throws IOException;
+
+  public abstract Attachment updateAttachment(Attachment attachment)
+    throws IOException;
+
+  public abstract Attachment deleteAttachment(Attachment attachment)
+    throws IOException;
+
+  public abstract SingleValue addMultiValue(Object value)
+    throws IOException;
+
+  public abstract SingleValue updateMultiValue(SingleValue value)
+    throws IOException;
+
+  public abstract SingleValue deleteMultiValue(SingleValue value)
+    throws IOException;
+
+  public abstract UnsupportedValue addUnsupportedValue(Map<String,?> values)
+    throws IOException;
+
+  public abstract UnsupportedValue updateUnsupportedValue(UnsupportedValue value)
+    throws IOException;
+
+  public abstract UnsupportedValue deleteUnsupportedValue(UnsupportedValue value)
+    throws IOException;
+
+  public abstract void deleteAllValues()
+    throws IOException;
+
 }
index 4089153040fd8df9690f6a9f0154395df386e4b5..406908ee985949fc32a50723d3111d8ab7a682b4 100644 (file)
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2011 James Ahlborn
+Copyright (c) 2013 James Ahlborn
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
@@ -19,101 +19,12 @@ USA
 
 package com.healthmarketscience.jackcess.complex;
 
-import java.io.IOException;
-
-import com.healthmarketscience.jackcess.Column;
-import com.healthmarketscience.jackcess.Table;
-import com.healthmarketscience.jackcess.Row;
-
 /**
- * Complex column info for a column holding multiple values per row.
+ * Complex column info for a column holding multiple simple values per row.
  *
  * @author James Ahlborn
  */
-public class MultiValueColumnInfo extends ComplexColumnInfo<SingleValue>
+public interface MultiValueColumnInfo extends ComplexColumnInfo<SingleValue>
 {
-  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 SingleValueImpl toValue(
-      ComplexValueForeignKey complexValueFk,
-      Row rawValue)
-  {
-    ComplexValue.Id id = getValueId(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_FK, value);
-  }
-
-  public static SingleValue newSingleValue(
-      ComplexValueForeignKey complexValueFk, Object value) {
-    return new SingleValueImpl(INVALID_ID, complexValueFk, value);
-  }
-
-
-  private static class SingleValueImpl extends ComplexValueImpl
-    implements SingleValue
-  {
-    private Object _value;
-
-    private SingleValueImpl(Id id, ComplexValueForeignKey complexValueFk,
-                            Object value)
-    {
-      super(id, complexValueFk);
-      _value = value;
-    }
-    
-    public Object get() {
-      return _value;
-    }
-
-    public void set(Object value) {
-      _value = value;
-    }
 
-    public void update() throws IOException {
-      getComplexValueForeignKey().updateMultiValue(this);
-    }
-    
-    public void delete() throws IOException {
-      getComplexValueForeignKey().deleteMultiValue(this);
-    }
-    
-    @Override
-    public String toString()
-    {
-      return "SingleValue(" + getComplexValueForeignKey() + "," + getId() +
-        ") " + get();
-    } 
-  }
 }
index b0b692429c450b6580ba6222ef12ce751c76570e..646ecfcdb3f76e69a73d1aaa2d616b39850bd21b 100644 (file)
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2011 James Ahlborn
+Copyright (c) 2013 James Ahlborn
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
@@ -19,114 +19,12 @@ USA
 
 package com.healthmarketscience.jackcess.complex;
 
-import java.io.IOException;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import com.healthmarketscience.jackcess.Column;
-import com.healthmarketscience.jackcess.Table;
-import com.healthmarketscience.jackcess.Row;
-
 /**
  * Complex column info for an unsupported complex type.
  *
  * @author James Ahlborn
  */
-public class UnsupportedColumnInfo extends ComplexColumnInfo<UnsupportedValue>
+public interface UnsupportedColumnInfo extends ComplexColumnInfo<UnsupportedValue>
 {
 
-  public UnsupportedColumnInfo(Column column, int complexId, Table typeObjTable,
-                               Table flatTable)
-    throws IOException
-  {
-    super(column, complexId, typeObjTable, flatTable);
-  }
-
-  public List<Column> getValueColumns() {
-    return getTypeColumns();
-  }
-
-  @Override
-  public ComplexDataType getType()
-  {
-    return ComplexDataType.UNSUPPORTED;
-  }
-
-  @Override
-  protected UnsupportedValueImpl toValue(
-      ComplexValueForeignKey complexValueFk,
-      Row rawValue)
-  {
-    ComplexValue.Id id = getValueId(rawValue);
-
-    Map<String,Object> values = new LinkedHashMap<String,Object>();
-    for(Column col : getValueColumns()) {
-      col.setRowValue(values, col.getRowValue(rawValue));
-    }
-
-    return new UnsupportedValueImpl(id, complexValueFk, values);
-  }
-
-  @Override
-  protected Object[] asRow(Object[] row, UnsupportedValue value) {
-    super.asRow(row, value);
-
-    Map<String,Object> values = value.getValues();
-    for(Column col : getValueColumns()) {
-      col.setRowValue(row, col.getRowValue(values));
-    }
-
-    return row;
-  }
-
-  public static UnsupportedValue newValue(Map<String,?> values) {
-    return newValue(INVALID_FK, values);
-  }
-
-  public static UnsupportedValue newValue(
-      ComplexValueForeignKey complexValueFk, Map<String,?> values) {
-    return new UnsupportedValueImpl(INVALID_ID, complexValueFk, 
-                                    new LinkedHashMap<String,Object>(values));
-  }
-  
-  private static class UnsupportedValueImpl extends ComplexValueImpl
-    implements UnsupportedValue
-  {
-    private Map<String,Object> _values;
-
-    private UnsupportedValueImpl(Id id, ComplexValueForeignKey complexValueFk,
-                                 Map<String,Object> values)
-    {
-      super(id, complexValueFk);
-      _values = values;
-    }
-
-    public Map<String,Object> getValues() {
-      return _values;
-    }
-    
-    public Object get(String columnName) {
-      return getValues().get(columnName);
-    }
-
-    public void set(String columnName, Object value) {
-      getValues().put(columnName, value);
-    }
-
-    public void update() throws IOException {
-      getComplexValueForeignKey().updateUnsupportedValue(this);
-    }
-    
-    public void delete() throws IOException {
-      getComplexValueForeignKey().deleteUnsupportedValue(this);
-    }
-    
-    @Override
-    public String toString()
-    {
-      return "UnsupportedValue(" + getComplexValueForeignKey() + "," + getId() +
-        ") " + getValues();
-    } 
-  }
 }
index 60c6fb7cc4e82cb3966a2458b8dc5730750c3194..db1f1cfe81902f825786b3757bdb2dda836d0c82 100644 (file)
@@ -1,5 +1,5 @@
 /*
-Copyright (c) 2011 James Ahlborn
+Copyright (c) 2013 James Ahlborn
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
@@ -19,16 +19,6 @@ USA
 
 package com.healthmarketscience.jackcess.complex;
 
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-
-import com.healthmarketscience.jackcess.Column;
-import com.healthmarketscience.jackcess.Row;
-import com.healthmarketscience.jackcess.Table;
-import com.healthmarketscience.jackcess.impl.ColumnImpl;
-
 /**
  * Complex column info for a column which tracking the version history of an
  * "append only" memo column.
@@ -40,179 +30,7 @@ import com.healthmarketscience.jackcess.impl.ColumnImpl;
  *
  * @author James Ahlborn
  */
-public class VersionHistoryColumnInfo extends ComplexColumnInfo<Version> 
+public interface 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());
-    ((ColumnImpl)versionedCol).setVersionHistoryColumn((ColumnImpl)getColumn());
-  }
-    
-  public Column getValueColumn() {
-    return _valueCol;
-  }
-
-  public Column getModifiedDateColumn() {
-    return _modifiedCol;
-  }
-  
-  @Override
-  public ComplexDataType getType() {
-    return ComplexDataType.VERSION_HISTORY;
-  }
-
-  @Override
-  public ComplexValue.Id updateValue(Version value) throws IOException {
-    throw new UnsupportedOperationException(
-        "This column does not support value updates");
-  }
-
-  @Override
-  public void deleteValue(Version value) throws IOException {
-    throw new UnsupportedOperationException(
-        "This column does not support value deletes");
-  }
-
-  @Override
-  public void deleteAllValues(int complexValueFk) throws IOException {
-    throw new UnsupportedOperationException(
-        "This column does not support value deletes");
-  }
-
-  @Override
-  protected List<Version> toValues(ComplexValueForeignKey complexValueFk,
-                                   List<Row> rawValues)
-    throws IOException
-  {
-    List<Version> versions = super.toValues(complexValueFk, rawValues);
-
-    // order versions newest to oldest
-    Collections.sort(versions);
-    
-    return versions;
-  }
-
-  @Override
-  protected VersionImpl toValue(ComplexValueForeignKey complexValueFk,
-                                Row rawValue) {
-    ComplexValue.Id id = getValueId(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_FK, value, modifiedDate);
-  }
-  
-  public static Version newVersion(ComplexValueForeignKey complexValueFk,
-                                   String value, Date modifiedDate) {
-    return new VersionImpl(INVALID_ID, complexValueFk, value, modifiedDate);
-  }
-
-
-  private static class VersionImpl extends ComplexValueImpl implements Version
-  {
-    private final String _value;
-    private final Date _modifiedDate;
-
-    private VersionImpl(Id 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().get();
-      int id2 = o.getId().get();
-      if(id1 != id2) {
-        return ((id1 > id2) ? -1 : 1);
-      }
-      id1 = getComplexValueForeignKey().get();
-      id2 = o.getComplexValueForeignKey().get();
-      return ((id1 > id2) ? -1 :
-              ((id1 < id2) ? 1 : 0));
-    }
-
-    public void update() throws IOException {
-      throw new UnsupportedOperationException(
-          "This column does not support value updates");
-    }
-    
-    public void delete() throws IOException {
-      throw new UnsupportedOperationException(
-          "This column does not support value deletes");
-    }
 
-    @Override
-    public String toString()
-    {
-      return "Version(" + getComplexValueForeignKey() + "," + getId() + ") " +
-        getModifiedDate() + ", " + getValue();
-    } 
-  }
-  
 }
index 2038556509f6dbacf0699cbb3e6d979d42e1d208..e940919755940668aeb28af0bb6d57c27a65d41b 100644 (file)
@@ -54,6 +54,8 @@ import java.util.regex.Pattern;
 import com.healthmarketscience.jackcess.complex.ComplexColumnInfo;
 import com.healthmarketscience.jackcess.complex.ComplexValue;
 import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
+import com.healthmarketscience.jackcess.impl.complex.ComplexColumnInfoImpl;
+import com.healthmarketscience.jackcess.impl.complex.ComplexValueForeignKeyImpl;
 import com.healthmarketscience.jackcess.impl.scsu.Compress;
 import com.healthmarketscience.jackcess.impl.scsu.EndOfInputException;
 import com.healthmarketscience.jackcess.impl.scsu.Expand;
@@ -327,7 +329,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
    */
   void postTableLoadInit() throws IOException {
     if(_complexInfo != null) {
-      _complexInfo.postTableLoadInit();
+      ((ComplexColumnInfoImpl<? extends ComplexValue>)_complexInfo)
+      .postTableLoadInit();
     }
   }
 
@@ -619,7 +622,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
       // treat like "binary" data
       return data;
     } else if (_type == DataType.COMPLEX_TYPE) {
-      return new ComplexValueForeignKey(this, buffer.getInt());
+      return new ComplexValueForeignKeyImpl(this, buffer.getInt());
     } else if(_type.isUnsupported()) {
       return rawDataWrapper(data);
     } else {
@@ -2057,7 +2060,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
          getTable().getNextComplexTypeAutoNumber() :
          // same value is shared across all ComplexType values in a row
          ((ComplexValueForeignKey)prevRowValue).get());
-      return new ComplexValueForeignKey(ColumnImpl.this, nextComplexAutoNum);
+      return new ComplexValueForeignKeyImpl(ColumnImpl.this, nextComplexAutoNum);
     }
 
     @Override
index f427247047ffe6f11c0431a2cece56c9f283e56d..9cf9b6821008daed71ceaba68f19d9e0e49a47c8 100644 (file)
@@ -38,6 +38,10 @@ import com.healthmarketscience.jackcess.complex.ComplexValue;
 import com.healthmarketscience.jackcess.complex.MultiValueColumnInfo;
 import com.healthmarketscience.jackcess.complex.UnsupportedColumnInfo;
 import com.healthmarketscience.jackcess.complex.VersionHistoryColumnInfo;
+import com.healthmarketscience.jackcess.impl.complex.AttachmentColumnInfoImpl;
+import com.healthmarketscience.jackcess.impl.complex.MultiValueColumnInfoImpl;
+import com.healthmarketscience.jackcess.impl.complex.UnsupportedColumnInfoImpl;
+import com.healthmarketscience.jackcess.impl.complex.VersionHistoryColumnInfoImpl;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
@@ -101,18 +105,18 @@ public class ComplexColumnSupport
     // we inspect the structore of the "type table" to determine what kind of
     // complex info we are dealing with
     if(isMultiValueColumn(typeObjTable)) {
-      return new MultiValueColumnInfo(column, complexTypeId, typeObjTable,
+      return new MultiValueColumnInfoImpl(column, complexTypeId, typeObjTable,
                                       flatTable);
     } else if(isAttachmentColumn(typeObjTable)) {
-      return new AttachmentColumnInfo(column, complexTypeId, typeObjTable,
+      return new AttachmentColumnInfoImpl(column, complexTypeId, typeObjTable,
                                       flatTable);
     } else if(isVersionHistoryColumn(typeObjTable)) {
-      return new VersionHistoryColumnInfo(column, complexTypeId, typeObjTable,
+      return new VersionHistoryColumnInfoImpl(column, complexTypeId, typeObjTable,
                                           flatTable);
     }
     
     LOG.warn("Unsupported complex column type " + typeObjTable.getName());
-    return new UnsupportedColumnInfo(column, complexTypeId, typeObjTable,
+    return new UnsupportedColumnInfoImpl(column, complexTypeId, typeObjTable,
                                      flatTable);
   }
 
diff --git a/src/java/com/healthmarketscience/jackcess/impl/complex/AttachmentColumnInfoImpl.java b/src/java/com/healthmarketscience/jackcess/impl/complex/AttachmentColumnInfoImpl.java
new file mode 100644 (file)
index 0000000..c0e5646
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+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.impl.complex;
+
+import java.io.IOException;
+import java.util.Date;
+
+import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.Row;
+import com.healthmarketscience.jackcess.Table;
+import com.healthmarketscience.jackcess.complex.Attachment;
+import com.healthmarketscience.jackcess.complex.AttachmentColumnInfo;
+import com.healthmarketscience.jackcess.complex.ComplexDataType;
+import com.healthmarketscience.jackcess.complex.ComplexValue;
+import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
+import com.healthmarketscience.jackcess.impl.ByteUtil;
+
+
+/**
+ * Complex column info for a column holding 0 or more attachments per row.
+ *
+ * @author James Ahlborn
+ */
+public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment>
+  implements AttachmentColumnInfo
+{
+  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 AttachmentColumnInfoImpl(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 AttachmentImpl toValue(ComplexValueForeignKey complexValueFk,
+                                   Row rawValue) {
+    ComplexValue.Id id = getValueId(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_FK, 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_FK, 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);
+  }
+
+  
+  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(Id 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;
+    }  
+
+    public void update() throws IOException {
+      getComplexValueForeignKey().updateAttachment(this);
+    }
+    
+    public void delete() throws IOException {
+      getComplexValueForeignKey().deleteAttachment(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/impl/complex/ComplexColumnInfoImpl.java b/src/java/com/healthmarketscience/jackcess/impl/complex/ComplexColumnInfoImpl.java
new file mode 100644 (file)
index 0000000..10d4ef5
--- /dev/null
@@ -0,0 +1,417 @@
+/*
+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.impl.complex;
+
+import java.io.IOException;
+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.Row;
+import com.healthmarketscience.jackcess.RowId;
+import com.healthmarketscience.jackcess.RuntimeIOException;
+import com.healthmarketscience.jackcess.Table;
+import com.healthmarketscience.jackcess.complex.ComplexColumnInfo;
+import com.healthmarketscience.jackcess.complex.ComplexDataType;
+import com.healthmarketscience.jackcess.complex.ComplexValue;
+import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
+import com.healthmarketscience.jackcess.impl.ColumnImpl;
+import com.healthmarketscience.jackcess.impl.TableImpl;
+
+/**
+ * Base class for the additional information tracked for complex columns.
+ *
+ * @author James Ahlborn
+ */
+public abstract class ComplexColumnInfoImpl<V extends ComplexValue> 
+  implements ComplexColumnInfo<V>
+{
+  private static final int INVALID_ID_VALUE = -1;
+  public static final ComplexValue.Id INVALID_ID = new ComplexValueIdImpl(
+      INVALID_ID_VALUE, null);
+  public static final ComplexValueForeignKey INVALID_FK =
+    new ComplexValueForeignKeyImpl(null, INVALID_ID_VALUE);
+
+  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 _complexValIdCursor;
+  
+  protected ComplexColumnInfoImpl(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.
+    List<Column> typeCols = new ArrayList<Column>();
+    List<Column> otherCols = new ArrayList<Column>();
+    diffFlatColumns(typeObjTable, flatTable, typeCols, otherCols);
+
+    _typeCols = Collections.unmodifiableList(typeCols);
+
+    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 void postTableLoadInit() throws IOException {
+    // nothing to do in base class
+  }
+  
+  public Column getColumn() {
+    return _column;
+  }
+
+  public Database getDatabase() {
+    return getColumn().getDatabase();
+  }
+
+  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<Row> getRawValues(int complexValueFk)
+    throws IOException
+  {
+    return getRawValues(complexValueFk, null);
+  }
+
+  private Iterator<Row> getComplexValFkIter(
+      int complexValueFk, Collection<String> columnNames)
+    throws IOException
+  {
+    if(_complexValIdCursor == null) {
+      _complexValIdCursor = new CursorBuilder(_flatTable)
+        .setIndexByColumns(_complexValFkCol)
+        .toIndexCursor();
+    }
+
+    return _complexValIdCursor.entryIterator(columnNames, complexValueFk);
+  }
+  
+  public List<Row> getRawValues(int complexValueFk,
+                                Collection<String> columnNames)
+    throws IOException
+  {
+    Iterator<Row> entryIter =
+      getComplexValFkIter(complexValueFk, columnNames);
+    if(!entryIter.hasNext()) {
+      return Collections.emptyList();
+    }
+
+    List<Row> values = new ArrayList<Row>();
+    while(entryIter.hasNext()) {
+      values.add(entryIter.next());
+    }
+    
+    return values;
+  }
+
+  public List<V> getValues(ComplexValueForeignKey complexValueFk)
+    throws IOException
+  {
+    List<Row> rawValues = getRawValues(complexValueFk.get());
+    if(rawValues.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    return toValues(complexValueFk, rawValues);
+  }
+  
+  protected List<V> toValues(ComplexValueForeignKey complexValueFk,
+                             List<Row> rawValues)
+    throws IOException
+  {
+    List<V> values = new ArrayList<V>();
+    for(Row rawValue : rawValues) {
+      values.add(toValue(complexValueFk, rawValue));
+    }
+
+    return values;
+  }
+
+  public ComplexValue.Id addRawValue(Map<String,?> rawValue)
+    throws IOException 
+  {
+    Object[] row = ((TableImpl)_flatTable).asRowWithRowId(rawValue);
+    _flatTable.addRow(row);
+    return getValueId(row);
+  }
+
+  public ComplexValue.Id addValue(V value) throws IOException {
+    Object[] row = asRow(newRowArray(), value);
+    _flatTable.addRow(row);
+    ComplexValue.Id id = getValueId(row);
+    value.setId(id);
+    return id;
+  }
+
+  public void addValues(Collection<? extends V> values) throws IOException {
+    for(V value : values) {
+      addValue(value);
+    }
+  }
+
+  public ComplexValue.Id updateRawValue(Row rawValue) throws IOException {
+    _flatTable.updateRow(rawValue);
+    return getValueId(rawValue);
+  }
+  
+  public ComplexValue.Id updateValue(V value) throws IOException {
+    ComplexValue.Id 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);
+    }
+  }
+
+  public void deleteRawValue(Row rawValue) throws IOException {
+    deleteRow(rawValue.getId());
+  }
+  
+  public void deleteValue(V value) throws IOException {
+    deleteRow(value.getId().getRowId());
+  }
+
+  public void deleteValues(Collection<? extends V> values) throws IOException {
+    for(V value : values) {
+      deleteValue(value);
+    }
+  }
+
+  public void deleteAllValues(int complexValueFk) throws IOException {
+    Iterator<Row> entryIter =
+      getComplexValFkIter(complexValueFk, Collections.<String>emptySet());
+    try {
+      while(entryIter.hasNext()) {
+        entryIter.next();
+        entryIter.remove();
+      }
+    } catch(RuntimeIOException e) {
+      throw (IOException)e.getCause();
+    }
+  }
+
+  public void deleteAllValues(ComplexValueForeignKey complexValueFk)
+    throws IOException
+  {
+    deleteAllValues(complexValueFk.get());
+  }
+
+  private void updateRow(ComplexValue.Id id, Object[] row) throws IOException {
+    ((TableImpl)_flatTable).updateRow(id.getRowId(), row);
+  }
+  
+  private void deleteRow(RowId rowId) throws IOException {
+    ((TableImpl)_flatTable).deleteRow(rowId);
+  }
+  
+  protected ComplexValueIdImpl getValueId(Row row) {
+    int idVal = (Integer)getPrimaryKeyColumn().getRowValue(row);
+    return new ComplexValueIdImpl(idVal, row.getId());
+  }
+
+  protected ComplexValueIdImpl getValueId(Object[] row) {
+    int idVal = (Integer)getPrimaryKeyColumn().getRowValue(row);
+    return new ComplexValueIdImpl(idVal, 
+                                  ((TableImpl)_flatTable).getRowId(row));
+  }
+
+  protected Object[] asRow(Object[] row, V value) {
+    ComplexValue.Id id = value.getId();
+    _pkCol.setRowValue(
+        row, ((id != INVALID_ID) ? id : Column.AUTO_NUMBER));
+    ComplexValueForeignKey cFk = value.getComplexValueForeignKey();
+    _complexValFkCol.setRowValue(
+        row, ((cFk != INVALID_FK) ? cFk : Column.AUTO_NUMBER));
+    return row;
+  }
+
+  private Object[] newRowArray() {
+    Object[] row = new Object[_flatTable.getColumnCount() + 1];
+    row[row.length - 1] = ColumnImpl.RETURN_ROW_ID;
+    return row;
+  }
+  
+  @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()) {
+      if(((TableImpl)typeObjTable).hasColumn(col.getName())) {
+        typeCols.add(col);
+      } else {
+        otherCols.add(col);
+      }  
+    } 
+  }
+  
+  public abstract ComplexDataType getType();
+
+  protected abstract V toValue(
+      ComplexValueForeignKey complexValueFk,
+      Row rawValues)
+    throws IOException;
+  
+  protected static abstract class ComplexValueImpl implements ComplexValue
+  {
+    private Id _id;
+    private ComplexValueForeignKey _complexValueFk;
+
+    protected ComplexValueImpl(Id id, ComplexValueForeignKey complexValueFk) {
+      _id = id;
+      _complexValueFk = complexValueFk;
+    }
+
+    public Id getId() {
+      return _id;
+    }
+
+    public void setId(Id id) {
+      if(_id == id) {
+        // harmless, ignore
+        return;
+      }
+      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 == complexValueFk) {
+        // harmless, ignore
+        return;
+      }
+      if(_complexValueFk != INVALID_FK) {
+        throw new IllegalStateException("complexValueFk may not be reset");
+      }
+      _complexValueFk = complexValueFk;
+    }
+
+    public Column getColumn() {
+      return _complexValueFk.getColumn();
+    }
+    
+    @Override
+    public int hashCode() {
+      return ((_id.get() * 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)));
+    }
+  }
+
+  /**
+   * Implementation of ComplexValue.Id.
+   */
+  private static final class ComplexValueIdImpl extends ComplexValue.Id
+  {
+    private static final long serialVersionUID = 20130318L;    
+
+    private final int _value;
+    private final RowId _rowId;
+
+    protected ComplexValueIdImpl(int value, RowId rowId) {
+      _value = value;
+      _rowId = rowId;
+    }
+    
+    @Override
+    public int get() {
+      return _value;
+    }
+
+    @Override
+    public RowId getRowId() {
+      return _rowId;
+    }
+  }
+  
+}
diff --git a/src/java/com/healthmarketscience/jackcess/impl/complex/ComplexValueForeignKeyImpl.java b/src/java/com/healthmarketscience/jackcess/impl/complex/ComplexValueForeignKeyImpl.java
new file mode 100644 (file)
index 0000000..d196ae5
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+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.impl.complex;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.Row;
+import com.healthmarketscience.jackcess.complex.Attachment;
+import com.healthmarketscience.jackcess.complex.AttachmentColumnInfo;
+import com.healthmarketscience.jackcess.complex.ComplexColumnInfo;
+import com.healthmarketscience.jackcess.complex.ComplexDataType;
+import com.healthmarketscience.jackcess.complex.ComplexValue;
+import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
+import com.healthmarketscience.jackcess.complex.MultiValueColumnInfo;
+import com.healthmarketscience.jackcess.complex.SingleValue;
+import com.healthmarketscience.jackcess.complex.UnsupportedColumnInfo;
+import com.healthmarketscience.jackcess.complex.UnsupportedValue;
+import com.healthmarketscience.jackcess.complex.Version;
+import com.healthmarketscience.jackcess.complex.VersionHistoryColumnInfo;
+
+/**
+ * Value which is returned for a complex column.  This value corresponds to a
+ * foreign key in a secondary table which contains the actual complex data for
+ * this row (which could be 0 or more complex values for a given row).  This
+ * class contains various convenience methods for interacting with the actual
+ * complex values.
+ * <p>
+ * This class will cache the associated complex values returned from one of
+ * the lookup methods.  The various modification methods will clear this cache
+ * automatically.  The {@link #reset} method may be called manually to clear
+ * this internal cache.
+ *
+ * @author James Ahlborn
+ */
+public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey
+{
+  private static final long serialVersionUID = 20110805L;  
+  
+  private transient final Column _column;
+  private final int _value;
+  private transient List<? extends ComplexValue> _values;
+  
+  public ComplexValueForeignKeyImpl(Column column, int value) {
+    _column = column;
+    _value = value;
+  }
+
+  @Override
+  public int get() {
+    return _value;
+  }
+
+  public Column getColumn() {
+    return _column;
+  }
+  
+  @Override
+  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();
+  }
+    
+  protected UnsupportedColumnInfo getUnsupportedInfo() {
+    return (UnsupportedColumnInfo)getComplexInfo();
+  }
+    
+  public int countValues() throws IOException {
+    return getComplexInfo().countValues(get());
+  }
+  
+  public List<Row> 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();
+  }
+  
+  @SuppressWarnings("unchecked")
+  public List<UnsupportedValue> getUnsupportedValues() throws IOException {
+    if(getComplexType() != ComplexDataType.UNSUPPORTED) {
+      throw new UnsupportedOperationException();
+    }
+    return (List<UnsupportedValue>)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 = VersionHistoryColumnInfoImpl.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 = AttachmentColumnInfoImpl.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 Attachment deleteAttachment(Attachment attachment) throws IOException {
+    reset();
+    getAttachmentInfo().deleteValue(attachment);
+    return attachment;
+  }
+  
+  public SingleValue addMultiValue(Object value) throws IOException {
+    reset();
+    SingleValue v = MultiValueColumnInfoImpl.newSingleValue(this, value);
+    getMultiValueInfo().addValue(v);
+    return v;
+  }
+  
+  public SingleValue updateMultiValue(SingleValue value) throws IOException {
+    reset();
+    getMultiValueInfo().updateValue(value);
+    return value;
+  }
+  
+  public SingleValue deleteMultiValue(SingleValue value) throws IOException {
+    reset();
+    getMultiValueInfo().deleteValue(value);
+    return value;
+  }
+  
+  public UnsupportedValue addUnsupportedValue(Map<String,?> values)
+    throws IOException
+  {
+    reset();
+    UnsupportedValue v = UnsupportedColumnInfoImpl.newValue(this, values);
+    getUnsupportedInfo().addValue(v);
+    return v;
+  }
+  
+  public UnsupportedValue updateUnsupportedValue(UnsupportedValue value)
+    throws IOException
+  {
+    reset();
+    getUnsupportedInfo().updateValue(value);
+    return value;
+  }
+  
+  public UnsupportedValue deleteUnsupportedValue(UnsupportedValue value)
+    throws IOException
+  {
+    reset();
+    getUnsupportedInfo().deleteValue(value);
+    return value;
+  }
+  
+  public void deleteAllValues() throws IOException {
+    reset();
+    getComplexInfo().deleteAllValues(this);
+  }
+  
+  @Override
+  public boolean equals(Object o) {
+    return(super.equals(o) &&
+           (_column == ((ComplexValueForeignKeyImpl)o)._column));
+  }
+}
diff --git a/src/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnInfoImpl.java b/src/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnInfoImpl.java
new file mode 100644 (file)
index 0000000..16b84e3
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+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.impl.complex;
+
+import java.io.IOException;
+
+import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.Row;
+import com.healthmarketscience.jackcess.Table;
+import com.healthmarketscience.jackcess.complex.ComplexDataType;
+import com.healthmarketscience.jackcess.complex.ComplexValue;
+import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
+import com.healthmarketscience.jackcess.complex.MultiValueColumnInfo;
+import com.healthmarketscience.jackcess.complex.SingleValue;
+
+/**
+ * Complex column info for a column holding multiple simple values per row.
+ *
+ * @author James Ahlborn
+ */
+public class MultiValueColumnInfoImpl extends ComplexColumnInfoImpl<SingleValue>
+  implements MultiValueColumnInfo
+{
+  private final Column _valueCol;
+  
+  public MultiValueColumnInfoImpl(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 SingleValueImpl toValue(
+      ComplexValueForeignKey complexValueFk,
+      Row rawValue)
+  {
+    ComplexValue.Id id = getValueId(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_FK, value);
+  }
+
+  public static SingleValue newSingleValue(
+      ComplexValueForeignKey complexValueFk, Object value) {
+    return new SingleValueImpl(INVALID_ID, complexValueFk, value);
+  }
+
+
+  private static class SingleValueImpl extends ComplexValueImpl
+    implements SingleValue
+  {
+    private Object _value;
+
+    private SingleValueImpl(Id id, ComplexValueForeignKey complexValueFk,
+                            Object value)
+    {
+      super(id, complexValueFk);
+      _value = value;
+    }
+    
+    public Object get() {
+      return _value;
+    }
+
+    public void set(Object value) {
+      _value = value;
+    }
+
+    public void update() throws IOException {
+      getComplexValueForeignKey().updateMultiValue(this);
+    }
+    
+    public void delete() throws IOException {
+      getComplexValueForeignKey().deleteMultiValue(this);
+    }
+    
+    @Override
+    public String toString()
+    {
+      return "SingleValue(" + getComplexValueForeignKey() + "," + getId() +
+        ") " + get();
+    } 
+  }
+}
diff --git a/src/java/com/healthmarketscience/jackcess/impl/complex/UnsupportedColumnInfoImpl.java b/src/java/com/healthmarketscience/jackcess/impl/complex/UnsupportedColumnInfoImpl.java
new file mode 100644 (file)
index 0000000..84dbcdb
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+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.impl.complex;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.Row;
+import com.healthmarketscience.jackcess.Table;
+import com.healthmarketscience.jackcess.complex.ComplexDataType;
+import com.healthmarketscience.jackcess.complex.ComplexValue;
+import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
+import com.healthmarketscience.jackcess.complex.UnsupportedColumnInfo;
+import com.healthmarketscience.jackcess.complex.UnsupportedValue;
+
+/**
+ * Complex column info for an unsupported complex type.
+ *
+ * @author James Ahlborn
+ */
+public class UnsupportedColumnInfoImpl 
+  extends ComplexColumnInfoImpl<UnsupportedValue> 
+  implements UnsupportedColumnInfo
+{
+
+  public UnsupportedColumnInfoImpl(Column column, int complexId, 
+                                   Table typeObjTable, Table flatTable)
+    throws IOException
+  {
+    super(column, complexId, typeObjTable, flatTable);
+  }
+
+  public List<Column> getValueColumns() {
+    return getTypeColumns();
+  }
+
+  @Override
+  public ComplexDataType getType()
+  {
+    return ComplexDataType.UNSUPPORTED;
+  }
+
+  @Override
+  protected UnsupportedValueImpl toValue(
+      ComplexValueForeignKey complexValueFk,
+      Row rawValue)
+  {
+    ComplexValue.Id id = getValueId(rawValue);
+
+    Map<String,Object> values = new LinkedHashMap<String,Object>();
+    for(Column col : getValueColumns()) {
+      col.setRowValue(values, col.getRowValue(rawValue));
+    }
+
+    return new UnsupportedValueImpl(id, complexValueFk, values);
+  }
+
+  @Override
+  protected Object[] asRow(Object[] row, UnsupportedValue value) {
+    super.asRow(row, value);
+
+    Map<String,Object> values = value.getValues();
+    for(Column col : getValueColumns()) {
+      col.setRowValue(row, col.getRowValue(values));
+    }
+
+    return row;
+  }
+
+  public static UnsupportedValue newValue(Map<String,?> values) {
+    return newValue(INVALID_FK, values);
+  }
+
+  public static UnsupportedValue newValue(
+      ComplexValueForeignKey complexValueFk, Map<String,?> values) {
+    return new UnsupportedValueImpl(INVALID_ID, complexValueFk, 
+                                    new LinkedHashMap<String,Object>(values));
+  }
+  
+  private static class UnsupportedValueImpl extends ComplexValueImpl
+    implements UnsupportedValue
+  {
+    private Map<String,Object> _values;
+
+    private UnsupportedValueImpl(Id id, ComplexValueForeignKey complexValueFk,
+                                 Map<String,Object> values)
+    {
+      super(id, complexValueFk);
+      _values = values;
+    }
+
+    public Map<String,Object> getValues() {
+      return _values;
+    }
+    
+    public Object get(String columnName) {
+      return getValues().get(columnName);
+    }
+
+    public void set(String columnName, Object value) {
+      getValues().put(columnName, value);
+    }
+
+    public void update() throws IOException {
+      getComplexValueForeignKey().updateUnsupportedValue(this);
+    }
+    
+    public void delete() throws IOException {
+      getComplexValueForeignKey().deleteUnsupportedValue(this);
+    }
+    
+    @Override
+    public String toString()
+    {
+      return "UnsupportedValue(" + getComplexValueForeignKey() + "," + getId() +
+        ") " + getValues();
+    } 
+  }
+}
diff --git a/src/java/com/healthmarketscience/jackcess/impl/complex/VersionHistoryColumnInfoImpl.java b/src/java/com/healthmarketscience/jackcess/impl/complex/VersionHistoryColumnInfoImpl.java
new file mode 100644 (file)
index 0000000..c1ebbfd
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+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.impl.complex;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.Row;
+import com.healthmarketscience.jackcess.Table;
+import com.healthmarketscience.jackcess.complex.ComplexDataType;
+import com.healthmarketscience.jackcess.complex.ComplexValue;
+import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
+import com.healthmarketscience.jackcess.complex.Version;
+import com.healthmarketscience.jackcess.complex.VersionHistoryColumnInfo;
+import com.healthmarketscience.jackcess.impl.ColumnImpl;
+
+/**
+ * Complex column info for a column which tracking the version history of an
+ * "append only" memo column.
+ * <p>
+ * Note, the strongly typed update/delete methods are <i>not</i> supported for
+ * version history columns (the data is supposed to be immutable).  That said,
+ * the "raw" update/delete methods are supported for those that <i>really</i>
+ * want to muck with the version history data.
+ *
+ * @author James Ahlborn
+ */
+public class VersionHistoryColumnInfoImpl extends ComplexColumnInfoImpl<Version> 
+  implements VersionHistoryColumnInfo
+{
+  private final Column _valueCol;
+  private final Column _modifiedCol;
+  
+  public VersionHistoryColumnInfoImpl(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());
+    ((ColumnImpl)versionedCol).setVersionHistoryColumn((ColumnImpl)getColumn());
+  }
+    
+  public Column getValueColumn() {
+    return _valueCol;
+  }
+
+  public Column getModifiedDateColumn() {
+    return _modifiedCol;
+  }
+  
+  @Override
+  public ComplexDataType getType() {
+    return ComplexDataType.VERSION_HISTORY;
+  }
+
+  @Override
+  public ComplexValue.Id updateValue(Version value) throws IOException {
+    throw new UnsupportedOperationException(
+        "This column does not support value updates");
+  }
+
+  @Override
+  public void deleteValue(Version value) throws IOException {
+    throw new UnsupportedOperationException(
+        "This column does not support value deletes");
+  }
+
+  @Override
+  public void deleteAllValues(int complexValueFk) throws IOException {
+    throw new UnsupportedOperationException(
+        "This column does not support value deletes");
+  }
+
+  @Override
+  protected List<Version> toValues(ComplexValueForeignKey complexValueFk,
+                                   List<Row> rawValues)
+    throws IOException
+  {
+    List<Version> versions = super.toValues(complexValueFk, rawValues);
+
+    // order versions newest to oldest
+    Collections.sort(versions);
+    
+    return versions;
+  }
+
+  @Override
+  protected VersionImpl toValue(ComplexValueForeignKey complexValueFk,
+                                Row rawValue) {
+    ComplexValue.Id id = getValueId(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_FK, value, modifiedDate);
+  }
+  
+  public static Version newVersion(ComplexValueForeignKey complexValueFk,
+                                   String value, Date modifiedDate) {
+    return new VersionImpl(INVALID_ID, complexValueFk, value, modifiedDate);
+  }
+
+
+  private static class VersionImpl extends ComplexValueImpl implements Version
+  {
+    private final String _value;
+    private final Date _modifiedDate;
+
+    private VersionImpl(Id 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().get();
+      int id2 = o.getId().get();
+      if(id1 != id2) {
+        return ((id1 > id2) ? -1 : 1);
+      }
+      id1 = getComplexValueForeignKey().get();
+      id2 = o.getComplexValueForeignKey().get();
+      return ((id1 > id2) ? -1 :
+              ((id1 < id2) ? 1 : 0));
+    }
+
+    public void update() throws IOException {
+      throw new UnsupportedOperationException(
+          "This column does not support value updates");
+    }
+    
+    public void delete() throws IOException {
+      throw new UnsupportedOperationException(
+          "This column does not support value deletes");
+    }
+
+    @Override
+    public String toString()
+    {
+      return "Version(" + getComplexValueForeignKey() + "," + getId() + ") " +
+        getModifiedDate() + ", " + getValue();
+    } 
+  }
+  
+}