/* Copyright (c) 2011 James Ahlborn Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ 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.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.CustomToStringStyle; import com.healthmarketscience.jackcess.impl.TableImpl; /** * Base class for the additional information tracked for complex columns. * * @author James Ahlborn */ public abstract class ComplexColumnInfoImpl implements ComplexColumnInfo { 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 _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 typeCols = new ArrayList(); List otherCols = new ArrayList(); 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 getTypeColumns() { return _typeCols; } @Override public int countValues(int complexValueFk) throws IOException { return getRawValues(complexValueFk, Collections.singleton(_complexValFkCol.getName())) .size(); } @Override public List getRawValues(int complexValueFk) throws IOException { return getRawValues(complexValueFk, null); } private Iterator getComplexValFkIter( int complexValueFk, Collection columnNames) throws IOException { if(_complexValIdCursor == null) { _complexValIdCursor = _flatTable.newCursor() .setIndexByColumns(_complexValFkCol) .toIndexCursor(); } return _complexValIdCursor.newEntryIterable(complexValueFk) .setColumnNames(columnNames).iterator(); } @Override public List getRawValues(int complexValueFk, Collection columnNames) throws IOException { Iterator entryIter = getComplexValFkIter(complexValueFk, columnNames); if(!entryIter.hasNext()) { return Collections.emptyList(); } List values = new ArrayList(); while(entryIter.hasNext()) { values.add(entryIter.next()); } return values; } @Override public List getValues(ComplexValueForeignKey complexValueFk) throws IOException { List rawValues = getRawValues(complexValueFk.get()); if(rawValues.isEmpty()) { return Collections.emptyList(); } return toValues(complexValueFk, rawValues); } protected List toValues(ComplexValueForeignKey complexValueFk, List rawValues) throws IOException { List values = new ArrayList(); for(Row rawValue : rawValues) { values.add(toValue(complexValueFk, rawValue)); } return values; } @Override public ComplexValue.Id addRawValue(Map rawValue) throws IOException { Object[] row = ((TableImpl)_flatTable).asRowWithRowId(rawValue); _flatTable.addRow(row); return getValueId(row); } @Override 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; } @Override public void addValues(Collection values) throws IOException { for(V value : values) { addValue(value); } } @Override public ComplexValue.Id updateRawValue(Row rawValue) throws IOException { _flatTable.updateRow(rawValue); return getValueId(rawValue); } @Override public ComplexValue.Id updateValue(V value) throws IOException { ComplexValue.Id id = value.getId(); updateRow(id, asRow(newRowArray(), value)); return id; } @Override public void updateValues(Collection values) throws IOException { for(V value : values) { updateValue(value); } } @Override public void deleteRawValue(Row rawValue) throws IOException { deleteRow(rawValue.getId()); } @Override public void deleteValue(V value) throws IOException { deleteRow(value.getId().getRowId()); } @Override public void deleteValues(Collection values) throws IOException { for(V value : values) { deleteValue(value); } } @Override public void deleteAllValues(int complexValueFk) throws IOException { Iterator entryIter = getComplexValFkIter(complexValueFk, Collections.emptySet()); try { while(entryIter.hasNext()) { entryIter.next(); entryIter.remove(); } } catch(RuntimeIOException e) { throw (IOException)e.getCause(); } } @Override 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) throws IOException { 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() { return CustomToStringStyle.valueBuilder(this) .append("complexType", getType()) .append("complexTypeId", _complexTypeId) .toString(); } protected static void diffFlatColumns(Table typeObjTable, Table flatTable, List typeCols, List 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); } } } @Override 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; } @Override public Id getId() { return _id; } @Override 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; } @Override public ComplexValueForeignKey getComplexValueForeignKey() { return _complexValueFk; } @Override 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; } @Override 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; } } }