* add unit tests for Row update/delete methods, add/update *FromMap methods
* add reason to unsupop throws for indexes
* remove static methods in CursorImpl/IndexCursorImpl
-- add RowId to ComplexValue
+* create ComplexValue.Id and keep RowId
import java.io.IOException;
import java.util.Date;
-import java.util.Map;
import com.healthmarketscience.jackcess.Column;
import com.healthmarketscience.jackcess.Row;
@Override
protected AttachmentImpl toValue(ComplexValueForeignKey complexValueFk,
Row rawValue) {
- int id = (Integer)getPrimaryKeyColumn().getRowValue(rawValue);
+ ComplexValue.Id id = getValueId(rawValue);
String url = (String)getFileUrlColumn().getRowValue(rawValue);
String name = (String)getFileNameColumn().getRowValue(rawValue);
String type = (String)getFileTypeColumn().getRowValue(rawValue);
}
public static Attachment newAttachment(byte[] data) {
- return newAttachment(INVALID_COMPLEX_VALUE_ID, data);
+ return newAttachment(INVALID_FK, data);
}
public static Attachment newAttachment(ComplexValueForeignKey complexValueFk,
String url, String name, String type, byte[] data,
Date timeStamp, Integer flags)
{
- return newAttachment(INVALID_COMPLEX_VALUE_ID, url, name, type, data,
+ return newAttachment(INVALID_FK, url, name, type, data,
timeStamp, flags);
}
private Date _timeStamp;
private Integer _flags;
- private AttachmentImpl(int id, ComplexValueForeignKey complexValueFk,
+ private AttachmentImpl(Id id, ComplexValueForeignKey complexValueFk,
String url, String name, String type, byte[] data,
Date timeStamp, Integer flags)
{
import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.Database;
import com.healthmarketscience.jackcess.IndexCursor;
-import com.healthmarketscience.jackcess.Table;
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.
*/
public abstract class ComplexColumnInfo<V extends ComplexValue>
{
- public static final int INVALID_ID = -1;
- public static final ComplexValueForeignKey INVALID_COMPLEX_VALUE_ID =
- new ComplexValueForeignKey(null, INVALID_ID);
+ 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);
private final Column _column;
private final int _complexTypeId;
private final List<Column> _typeCols;
private final Column _pkCol;
private final Column _complexValFkCol;
- private IndexCursor _pkCursor;
private IndexCursor _complexValIdCursor;
protected ComplexColumnInfo(Column column, int complexTypeId,
return values;
}
- public int addRawValue(Row rawValue) throws IOException {
- Object[] row = _flatTable.asRow(rawValue);
+ public ComplexValue.Id addRawValue(Map<String,?> rawValue)
+ throws IOException
+ {
+ Object[] row = ((TableImpl)_flatTable).asRowWithRowId(rawValue);
_flatTable.addRow(row);
- return (Integer)_pkCol.getRowValue(row);
+ return getValueId(row);
}
- public int addValue(V value) throws IOException {
+ public ComplexValue.Id addValue(V value) throws IOException {
Object[] row = asRow(newRowArray(), value);
_flatTable.addRow(row);
- int id = (Integer)_pkCol.getRowValue(row);
+ ComplexValue.Id id = getValueId(row);
value.setId(id);
return id;
}
}
}
- public int updateRawValue(Row rawValue) throws IOException {
- Integer id = (Integer)_pkCol.getRowValue(rawValue);
- updateRow(id, _flatTable.asUpdateRow(rawValue));
- return id;
+ public ComplexValue.Id updateRawValue(Row rawValue) throws IOException {
+ _flatTable.updateRow(rawValue);
+ return getValueId(rawValue);
}
- public int updateValue(V value) throws IOException {
- int id = value.getId();
+ public ComplexValue.Id updateValue(V value) throws IOException {
+ ComplexValue.Id id = value.getId();
updateRow(id, asRow(newRowArray(), value));
return id;
}
}
public void deleteRawValue(Row rawValue) throws IOException {
- deleteRow((Integer)_pkCol.getRowValue(rawValue));
+ deleteRow(rawValue.getId());
}
public void deleteValue(V value) throws IOException {
- deleteRow(value.getId());
+ deleteRow(value.getId().getRowId());
}
public void deleteValues(Collection<? extends V> values) throws IOException {
entryIter.next();
entryIter.remove();
}
- } catch(RuntimeException e) {
- if(e.getCause() instanceof IOException) {
- throw (IOException)e.getCause();
- }
- throw e;
+ } catch(RuntimeIOException e) {
+ throw (IOException)e.getCause();
}
}
deleteAllValues(complexValueFk.get());
}
- private void moveToRow(Integer id) throws IOException {
- if(_pkCursor == null) {
- _pkCursor = new CursorBuilder(_flatTable)
- .setIndexByColumns(_pkCol)
- .toIndexCursor();
- }
-
- if(!_pkCursor.findFirstRowByEntry(id)) {
- throw new IllegalArgumentException("Row with id " + id +
- " does not exist");
- }
- }
-
- private void updateRow(Integer id, Object[] row) throws IOException {
- moveToRow(id);
- _pkCursor.updateCurrentRow(row);
+ private void updateRow(ComplexValue.Id id, Object[] row) throws IOException {
+ ((TableImpl)_flatTable).updateRow(id.getRowId(), row);
}
- private void deleteRow(Integer id) throws IOException {
- moveToRow(id);
- _pkCursor.deleteCurrentRow();
+ 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, (RowId)row[row.length - 1]);
+ }
+
protected Object[] asRow(Object[] row, V value) {
- int id = value.getId();
- _pkCol.setRowValue(row, ((id != INVALID_ID) ? id : Column.AUTO_NUMBER));
- int cId = value.getComplexValueForeignKey().get();
+ ComplexValue.Id id = value.getId();
+ _pkCol.setRowValue(
+ row, ((id != INVALID_ID) ? id : Column.AUTO_NUMBER));
+ ComplexValueForeignKey cFk = value.getComplexValueForeignKey();
_complexValFkCol.setRowValue(
- row, ((cId != INVALID_ID) ? cId : Column.AUTO_NUMBER));
+ row, ((cFk != INVALID_FK) ? cFk : Column.AUTO_NUMBER));
return row;
}
private Object[] newRowArray() {
- return new Object[_flatTable.getColumnCount()];
+ Object[] row = new Object[_flatTable.getColumnCount() + 1];
+ row[row.length - 1] = ColumnImpl.RETURN_ROW_ID;
+ return row;
}
@Override
// each "flat"" table has the columns from the "type" table, plus some
// others. separate the "flat" columns into these 2 buckets
for(Column col : flatTable.getColumns()) {
- boolean found = false;
- try {
- typeObjTable.getColumn(col.getName());
- found = true;
- } catch(IllegalArgumentException e) {
- // FIXME better way to test this?
- }
- if(found) {
+ if(((TableImpl)typeObjTable).hasColumn(col.getName())) {
typeCols.add(col);
} else {
otherCols.add(col);
protected static abstract class ComplexValueImpl implements ComplexValue
{
- private int _id;
+ private Id _id;
private ComplexValueForeignKey _complexValueFk;
- protected ComplexValueImpl(int id, ComplexValueForeignKey complexValueFk) {
+ protected ComplexValueImpl(Id id, ComplexValueForeignKey complexValueFk) {
_id = id;
_complexValueFk = complexValueFk;
}
- public int getId() {
+ public Id getId() {
return _id;
}
- public void setId(int id) {
+ public void setId(Id id) {
+ if(_id == id) {
+ // harmless, ignore
+ return;
+ }
if(_id != INVALID_ID) {
throw new IllegalStateException("id may not be reset");
}
public void setComplexValueForeignKey(ComplexValueForeignKey complexValueFk)
{
- if(_complexValueFk != INVALID_COMPLEX_VALUE_ID) {
+ if(_complexValueFk == complexValueFk) {
+ // harmless, ignore
+ return;
+ }
+ if(_complexValueFk != INVALID_FK) {
throw new IllegalStateException("complexValueFk may not be reset");
}
_complexValueFk = complexValueFk;
@Override
public int hashCode() {
- return ((_id * 37) ^ _complexValueFk.hashCode());
+ return ((_id.get() * 37) ^ _complexValueFk.hashCode());
}
@Override
_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;
+ }
+
+ @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)));
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.valueOf(_value);
+ }
+ }
}
import java.io.IOException;
import com.healthmarketscience.jackcess.Column;
+import com.healthmarketscience.jackcess.RowId;
/**
* Base interface for a value in a complex column (where there may be multiple
* @return the current id or {@link ComplexColumnInfo#INVALID_ID} for a new,
* unsaved value.
*/
- public int getId();
+ public Id getId();
- public void setId(int newId);
+ /**
+ * Called once when a new ComplexValue is saved to set the new unique
+ * identifier.
+ */
+ public void setId(Id newId);
/**
* Returns the foreign key identifier for this complex value (this value is
* the same for all values in the same row of the main table).
*
- * @return the current id or {@link ComplexColumnInfo#INVALID_COMPLEX_VALUE_ID}
+ * @return the current id or {@link ComplexColumnInfo#INVALID_FK}
* for a new, unsaved value.
*/
public ComplexValueForeignKey getComplexValueForeignKey();
*/
public void delete() throws IOException;
+
+ /**
+ * Identifier for a ComplexValue. Only valid for comparing complex values
+ * for the same column.
+ */
+ public abstract class Id extends Number
+ {
+ private static final long serialVersionUID = 20130318L;
+
+ /**
+ * 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).
+ */
+ public abstract int get();
+
+ /**
+ * Returns the rowId of this ComplexValue within the secondary table.
+ */
+ public abstract RowId getRowId();
+ }
}
package com.healthmarketscience.jackcess.complex;
import java.io.IOException;
-import java.util.Map;
import com.healthmarketscience.jackcess.Column;
import com.healthmarketscience.jackcess.Table;
ComplexValueForeignKey complexValueFk,
Row rawValue)
{
- int id = (Integer)getPrimaryKeyColumn().getRowValue(rawValue);
+ ComplexValue.Id id = getValueId(rawValue);
Object value = getValueColumn().getRowValue(rawValue);
return new SingleValueImpl(id, complexValueFk, value);
}
public static SingleValue newSingleValue(Object value) {
- return newSingleValue(INVALID_COMPLEX_VALUE_ID, value);
+ return newSingleValue(INVALID_FK, value);
}
public static SingleValue newSingleValue(
{
private Object _value;
- private SingleValueImpl(int id, ComplexValueForeignKey complexValueFk,
+ private SingleValueImpl(Id id, ComplexValueForeignKey complexValueFk,
Object value)
{
super(id, complexValueFk);
import com.healthmarketscience.jackcess.Column;
import com.healthmarketscience.jackcess.Table;
import com.healthmarketscience.jackcess.Row;
-import com.healthmarketscience.jackcess.impl.RowImpl;
/**
* Complex column info for an unsupported complex type.
ComplexValueForeignKey complexValueFk,
Row rawValue)
{
- int id = (Integer)getPrimaryKeyColumn().getRowValue(rawValue);
+ ComplexValue.Id id = getValueId(rawValue);
Map<String,Object> values = new LinkedHashMap<String,Object>();
for(Column col : getValueColumns()) {
}
public static UnsupportedValue newValue(Map<String,?> values) {
- return newValue(INVALID_COMPLEX_VALUE_ID, values);
+ return newValue(INVALID_FK, values);
}
public static UnsupportedValue newValue(
{
private Map<String,Object> _values;
- private UnsupportedValueImpl(int id, ComplexValueForeignKey complexValueFk,
+ private UnsupportedValueImpl(Id id, ComplexValueForeignKey complexValueFk,
Map<String,Object> values)
{
super(id, complexValueFk);
import java.util.Collections;
import java.util.Date;
import java.util.List;
-import java.util.Map;
import com.healthmarketscience.jackcess.Column;
import com.healthmarketscience.jackcess.Row;
}
@Override
- public int updateValue(Version value) throws IOException {
+ public ComplexValue.Id updateValue(Version value) throws IOException {
throw new UnsupportedOperationException(
"This column does not support value updates");
}
@Override
protected VersionImpl toValue(ComplexValueForeignKey complexValueFk,
Row rawValue) {
- int id = (Integer)getPrimaryKeyColumn().getRowValue(rawValue);
+ ComplexValue.Id id = getValueId(rawValue);
String value = (String)getValueColumn().getRowValue(rawValue);
Date modifiedDate = (Date)getModifiedDateColumn().getRowValue(rawValue);
}
public static Version newVersion(String value, Date modifiedDate) {
- return newVersion(INVALID_COMPLEX_VALUE_ID, value, modifiedDate);
+ return newVersion(INVALID_FK, value, modifiedDate);
}
public static Version newVersion(ComplexValueForeignKey complexValueFk,
private final String _value;
private final Date _modifiedDate;
- private VersionImpl(int id, ComplexValueForeignKey complexValueFk,
+ private VersionImpl(Id id, ComplexValueForeignKey complexValueFk,
String value, Date modifiedDate)
{
super(id, complexValueFk);
// use id, then complexValueFk to break ties (although we really
// shouldn't be comparing across different columns)
- int id1 = getId();
- int id2 = o.getId();
+ int id1 = getId().get();
+ int id2 = o.getId().get();
if(id1 != id2) {
return ((id1 > id2) ? -1 : 1);
}
public class ColumnImpl implements Column, Comparable<ColumnImpl> {
private static final Log LOG = LogFactory.getLog(ColumnImpl.class);
+
+ /**
+ * Placeholder object for adding rows which indicates that the caller wants
+ * the RowId of the new row. Must be added as an extra value at the end of
+ * the row values array.
+ * @see TableImpl#asRowWithRowId
+ * @usage _intermediate_field_
+ */
+ public static final Object RETURN_ROW_ID = "<RETURN_ROW_ID>";
/**
* Access stores numeric dates in days. Java stores them in milliseconds.
@Override
public String toString() {
- return "Row id=[" + _id + "], content=" + super.toString();
+ return "Row[" + _id + "] " + super.toString();
}
}
import com.healthmarketscience.jackcess.IndexBuilder;
import com.healthmarketscience.jackcess.PropertyMap;
import com.healthmarketscience.jackcess.Row;
+import com.healthmarketscience.jackcess.RowId;
import com.healthmarketscience.jackcess.Table;
import com.healthmarketscience.jackcess.util.ErrorHandler;
import org.apache.commons.logging.Log;
throw new IllegalArgumentException("Column with name " + name +
" does not exist in this table");
}
+
+ public boolean hasColumn(String name) {
+ for(ColumnImpl column : _columns) {
+ if(column.getName().equalsIgnoreCase(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
public PropertyMap getProperties() throws IOException {
if(_props == null) {
}
public Row deleteRow(Row row) throws IOException {
- deleteRow(getDefaultCursor().getRowState(), (RowIdImpl)row.getId());
+ deleteRow(row.getId());
return row;
}
+ /**
+ * Delete the row with the given id. Provided RowId must have previously
+ * been returned from this Table.
+ * @return the given rowId
+ * @throws IllegalStateException if the given row is not valid
+ * @usage _intermediate_method_
+ */
+ public RowId deleteRow(RowId rowId) throws IOException {
+ deleteRow(getDefaultCursor().getRowState(), (RowIdImpl)rowId);
+ return rowId;
+ }
+
/**
* Delete the row for the given rowId.
* @usage _advanced_method_
}
public Object[] asRow(Map<String,?> rowMap) {
- return asRow(rowMap, null);
+ return asRow(rowMap, null, false);
+ }
+
+ /**
+ * Converts a map of columnName -> columnValue to an array of row values
+ * appropriate for a call to {@link #addRow(Object...)}, where the generated
+ * RowId will be an extra value at the end of the array.
+ * @see ColumnImpl#RETURN_ROW_ID
+ * @usage _intermediate_method_
+ */
+ public Object[] asRowWithRowId(Map<String,?> rowMap) {
+ return asRow(rowMap, null, true);
}
public Object[] asUpdateRow(Map<String,?> rowMap) {
- return asRow(rowMap, Column.KEEP_VALUE);
+ return asRow(rowMap, Column.KEEP_VALUE, false);
}
/**
* Converts a map of columnName -> columnValue to an array of row values.
*/
- private Object[] asRow(Map<String,?> rowMap, Object defaultValue)
+ private Object[] asRow(Map<String,?> rowMap, Object defaultValue,
+ boolean returnRowId)
{
- Object[] row = new Object[_columns.size()];
+ int len = _columns.size();
+ if(returnRowId) {
+ ++len;
+ }
+ Object[] row = new Object[len];
if(defaultValue != null) {
Arrays.fill(row, defaultValue);
}
+ if(returnRowId) {
+ row[len - 1] = ColumnImpl.RETURN_ROW_ID;
+ }
if(rowMap == null) {
return row;
}
List<Object[]> dupeRows = null;
ByteBuffer[] rowData = new ByteBuffer[rows.size()];
+ int numCols = _columns.size();
for (int i = 0; i < rows.size(); i++) {
// we need to make sure the row is the right length and is an Object[]
// they need that info they should use a row array of the right
// size/type!
Object[] row = rows.get(i);
- if((row.length < _columns.size()) || (row.getClass() != Object[].class)) {
- row = dupeRow(row, _columns.size());
+ if((row.length < numCols) || (row.getClass() != Object[].class)) {
+ row = dupeRow(row, numCols);
// copy the input rows to a modifiable list so we can update the
// elements
if(dupeRows == null) {
for(IndexData indexData : _indexDatas) {
indexData.addRow(row, rowId);
}
+
+ // return rowTd if desired
+ if((row.length > numCols) && (row[numCols] == ColumnImpl.RETURN_ROW_ID)) {
+ row[numCols] = rowId;
+ }
}
writeDataPage(dataPage, pageNumber);
getDefaultCursor().getRowState(), (RowIdImpl)row.getId(), row);
}
+ /**
+ * Update the row with the given id. Provided RowId must have previously
+ * been returned from this Table.
+ * @return the given row, updated with the current row values
+ * @throws IllegalStateException if the given row is not valid, or deleted.
+ * @usage _intermediate_method_
+ */
+ public Object[] updateRow(RowId rowId, Object... row) throws IOException {
+ return updateRow(
+ getDefaultCursor().getRowState(), (RowIdImpl)rowId, row);
+ }
+
public <M extends Map<String,Object>> M updateRowFromMap(
RowState rowState, RowIdImpl rowId, M row)
throws IOException
import com.healthmarketscience.jackcess.CursorBuilder;
import com.healthmarketscience.jackcess.Index;
import com.healthmarketscience.jackcess.IndexCursor;
-import com.healthmarketscience.jackcess.Table;
import com.healthmarketscience.jackcess.Row;
+import com.healthmarketscience.jackcess.RuntimeIOException;
+import com.healthmarketscience.jackcess.Table;
import com.healthmarketscience.jackcess.impl.IndexImpl;
/**
* @param columnNames desired columns in the from table row
*/
public Iterator<Row> findRows(Map<String,?> fromRow,
- Collection<String> columnNames)
+ Collection<String> columnNames)
{
toEntryValues(fromRow);
return _toCursor.entryIterator(columnNames, _entryValues);
* @usage _intermediate_method_
*/
public Iterator<Row> findRows(Object[] fromRow,
- Collection<String> columnNames)
+ Collection<String> columnNames)
{
toEntryValues(fromRow);
return _toCursor.entryIterator(columnNames, _entryValues);