You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ComplexColumnInfo.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. /*
  2. Copyright (c) 2011 James Ahlborn
  3. This library is free software; you can redistribute it and/or
  4. modify it under the terms of the GNU Lesser General Public
  5. License as published by the Free Software Foundation; either
  6. version 2.1 of the License, or (at your option) any later version.
  7. This library is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  10. Lesser General Public License for more details.
  11. You should have received a copy of the GNU Lesser General Public
  12. License along with this library; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  14. USA
  15. */
  16. package com.healthmarketscience.jackcess.complex;
  17. import java.io.IOException;
  18. import java.nio.ByteBuffer;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.Iterator;
  23. import java.util.List;
  24. import java.util.Map;
  25. import com.healthmarketscience.jackcess.Column;
  26. import com.healthmarketscience.jackcess.CursorBuilder;
  27. import com.healthmarketscience.jackcess.DataType;
  28. import com.healthmarketscience.jackcess.Database;
  29. import com.healthmarketscience.jackcess.IndexCursor;
  30. import com.healthmarketscience.jackcess.JetFormat;
  31. import com.healthmarketscience.jackcess.PageChannel;
  32. import com.healthmarketscience.jackcess.Table;
  33. import org.apache.commons.logging.Log;
  34. import org.apache.commons.logging.LogFactory;
  35. /**
  36. * Base class for the additional information tracked for complex columns.
  37. *
  38. * @author James Ahlborn
  39. */
  40. public abstract class ComplexColumnInfo<V extends ComplexValue>
  41. {
  42. private static final Log LOG = LogFactory.getLog(Column.class);
  43. public static final int INVALID_ID = -1;
  44. public static final ComplexValueForeignKey INVALID_COMPLEX_VALUE_ID =
  45. new ComplexValueForeignKey(null, INVALID_ID);
  46. private static final String COL_COMPLEX_TYPE_OBJECT_ID = "ComplexTypeObjectID";
  47. private static final String COL_TABLE_ID = "ConceptualTableID";
  48. private static final String COL_FLAT_TABLE_ID = "FlatTableID";
  49. private final Column _column;
  50. private final int _complexTypeId;
  51. private final Table _flatTable;
  52. private final List<Column> _typeCols;
  53. private final Column _pkCol;
  54. private final Column _complexValFkCol;
  55. private IndexCursor _pkCursor;
  56. private IndexCursor _complexValIdCursor;
  57. protected ComplexColumnInfo(Column column, int complexTypeId,
  58. Table typeObjTable, Table flatTable)
  59. throws IOException
  60. {
  61. _column = column;
  62. _complexTypeId = complexTypeId;
  63. _flatTable = flatTable;
  64. // the flat table has all the "value" columns and 2 extra columns, a
  65. // primary key for each row, and a LONG value which is essentially a
  66. // foreign key to the main table.
  67. List<Column> typeCols = new ArrayList<Column>();
  68. List<Column> otherCols = new ArrayList<Column>();
  69. diffFlatColumns(typeObjTable, flatTable, typeCols, otherCols);
  70. _typeCols = Collections.unmodifiableList(typeCols);
  71. Column pkCol = null;
  72. Column complexValFkCol = null;
  73. for(Column col : otherCols) {
  74. if(col.isAutoNumber()) {
  75. pkCol = col;
  76. } else if(col.getType() == DataType.LONG) {
  77. complexValFkCol = col;
  78. }
  79. }
  80. if((pkCol == null) || (complexValFkCol == null)) {
  81. throw new IOException("Could not find expected columns in flat table " +
  82. flatTable.getName() + " for complex column with id "
  83. + complexTypeId);
  84. }
  85. _pkCol = pkCol;
  86. _complexValFkCol = complexValFkCol;
  87. }
  88. public static ComplexColumnInfo<? extends ComplexValue> create(
  89. Column column, ByteBuffer buffer, int offset)
  90. throws IOException
  91. {
  92. int complexTypeId = buffer.getInt(
  93. offset + column.getFormat().OFFSET_COLUMN_COMPLEX_ID);
  94. Database db = column.getDatabase();
  95. Table complexColumns = db.getSystemComplexColumns();
  96. IndexCursor cursor = IndexCursor.createCursor(
  97. complexColumns, complexColumns.getPrimaryKeyIndex());
  98. if(!cursor.findFirstRowByEntry(complexTypeId)) {
  99. throw new IOException(
  100. "Could not find complex column info for complex column with id " +
  101. complexTypeId);
  102. }
  103. Map<String,Object> cColRow = cursor.getCurrentRow();
  104. int tableId = (Integer)cColRow.get(COL_TABLE_ID);
  105. if(tableId != column.getTable().getTableDefPageNumber()) {
  106. throw new IOException(
  107. "Found complex column for table " + tableId + " but expected table " +
  108. column.getTable().getTableDefPageNumber());
  109. }
  110. int flatTableId = (Integer)cColRow.get(COL_FLAT_TABLE_ID);
  111. int typeObjId = (Integer)cColRow.get(COL_COMPLEX_TYPE_OBJECT_ID);
  112. Table typeObjTable = db.getTable(typeObjId);
  113. Table flatTable = db.getTable(flatTableId);
  114. if((typeObjTable == null) || (flatTable == null)) {
  115. throw new IOException(
  116. "Could not find supporting tables (" + typeObjId + ", " + flatTableId
  117. + ") for complex column with id " + complexTypeId);
  118. }
  119. // we inspect the structore of the "type table" to determine what kind of
  120. // complex info we are dealing with
  121. if(MultiValueColumnInfo.isMultiValueColumn(typeObjTable)) {
  122. return new MultiValueColumnInfo(column, complexTypeId, typeObjTable,
  123. flatTable);
  124. } else if(AttachmentColumnInfo.isAttachmentColumn(typeObjTable)) {
  125. return new AttachmentColumnInfo(column, complexTypeId, typeObjTable,
  126. flatTable);
  127. } else if(VersionHistoryColumnInfo.isVersionHistoryColumn(typeObjTable)) {
  128. return new VersionHistoryColumnInfo(column, complexTypeId, typeObjTable,
  129. flatTable);
  130. }
  131. LOG.warn("Unsupported complex column type " + typeObjTable.getName());
  132. return new UnsupportedColumnInfo(column, complexTypeId, typeObjTable,
  133. flatTable);
  134. }
  135. public void postTableLoadInit() throws IOException {
  136. // nothing to do in base class
  137. }
  138. public Column getColumn() {
  139. return _column;
  140. }
  141. public Database getDatabase() {
  142. return getColumn().getDatabase();
  143. }
  144. public JetFormat getFormat() {
  145. return getDatabase().getFormat();
  146. }
  147. public PageChannel getPageChannel() {
  148. return getDatabase().getPageChannel();
  149. }
  150. public Column getPrimaryKeyColumn() {
  151. return _pkCol;
  152. }
  153. public Column getComplexValueForeignKeyColumn() {
  154. return _complexValFkCol;
  155. }
  156. protected List<Column> getTypeColumns() {
  157. return _typeCols;
  158. }
  159. public int countValues(int complexValueFk) throws IOException {
  160. return getRawValues(complexValueFk,
  161. Collections.singleton(_complexValFkCol.getName()))
  162. .size();
  163. }
  164. public List<Map<String,Object>> getRawValues(int complexValueFk)
  165. throws IOException
  166. {
  167. return getRawValues(complexValueFk, null);
  168. }
  169. private Iterator<Map<String,Object>> getComplexValFkIter(
  170. int complexValueFk, Collection<String> columnNames)
  171. throws IOException
  172. {
  173. if(_complexValIdCursor == null) {
  174. _complexValIdCursor = new CursorBuilder(_flatTable)
  175. .setIndexByColumns(_complexValFkCol)
  176. .toIndexCursor();
  177. }
  178. return _complexValIdCursor.entryIterator(columnNames, complexValueFk);
  179. }
  180. public List<Map<String,Object>> getRawValues(int complexValueFk,
  181. Collection<String> columnNames)
  182. throws IOException
  183. {
  184. Iterator<Map<String,Object>> entryIter =
  185. getComplexValFkIter(complexValueFk, columnNames);
  186. if(!entryIter.hasNext()) {
  187. return Collections.emptyList();
  188. }
  189. List<Map<String,Object>> values = new ArrayList<Map<String,Object>>();
  190. while(entryIter.hasNext()) {
  191. values.add(entryIter.next());
  192. }
  193. return values;
  194. }
  195. public List<V> getValues(ComplexValueForeignKey complexValueFk)
  196. throws IOException
  197. {
  198. List<Map<String,Object>> rawValues = getRawValues(complexValueFk.get());
  199. if(rawValues.isEmpty()) {
  200. return Collections.emptyList();
  201. }
  202. return toValues(complexValueFk, rawValues);
  203. }
  204. protected List<V> toValues(ComplexValueForeignKey complexValueFk,
  205. List<Map<String,Object>> rawValues)
  206. throws IOException
  207. {
  208. List<V> values = new ArrayList<V>();
  209. for(Map<String,Object> rawValue : rawValues) {
  210. values.add(toValue(complexValueFk, rawValue));
  211. }
  212. return values;
  213. }
  214. public int addRawValue(Map<String,Object> rawValue) throws IOException {
  215. Object[] row = _flatTable.asRow(rawValue);
  216. _flatTable.addRow(row);
  217. return (Integer)_pkCol.getRowValue(row);
  218. }
  219. public int addValue(V value) throws IOException {
  220. Object[] row = asRow(newRowArray(), value);
  221. _flatTable.addRow(row);
  222. int id = (Integer)_pkCol.getRowValue(row);
  223. value.setId(id);
  224. return id;
  225. }
  226. public void addValues(Collection<? extends V> values) throws IOException {
  227. for(V value : values) {
  228. addValue(value);
  229. }
  230. }
  231. public int updateRawValue(Map<String,Object> rawValue) throws IOException {
  232. Integer id = (Integer)_pkCol.getRowValue(rawValue);
  233. updateRow(id, _flatTable.asUpdateRow(rawValue));
  234. return id;
  235. }
  236. public int updateValue(V value) throws IOException {
  237. int id = value.getId();
  238. updateRow(id, asRow(newRowArray(), value));
  239. return id;
  240. }
  241. public void updateValues(Collection<? extends V> values) throws IOException {
  242. for(V value : values) {
  243. updateValue(value);
  244. }
  245. }
  246. public void deleteRawValue(Map<String,Object> rawValue) throws IOException {
  247. deleteRow((Integer)_pkCol.getRowValue(rawValue));
  248. }
  249. public void deleteValue(V value) throws IOException {
  250. deleteRow(value.getId());
  251. }
  252. public void deleteValues(Collection<? extends V> values) throws IOException {
  253. for(V value : values) {
  254. deleteValue(value);
  255. }
  256. }
  257. public void deleteAllValues(int complexValueFk) throws IOException {
  258. Iterator<Map<String,Object>> entryIter =
  259. getComplexValFkIter(complexValueFk, Collections.<String>emptySet());
  260. try {
  261. while(entryIter.hasNext()) {
  262. entryIter.next();
  263. entryIter.remove();
  264. }
  265. } catch(RuntimeException e) {
  266. if(e.getCause() instanceof IOException) {
  267. throw (IOException)e.getCause();
  268. }
  269. throw e;
  270. }
  271. }
  272. public void deleteAllValues(ComplexValueForeignKey complexValueFk)
  273. throws IOException
  274. {
  275. deleteAllValues(complexValueFk.get());
  276. }
  277. private void moveToRow(Integer id) throws IOException {
  278. if(_pkCursor == null) {
  279. _pkCursor = new CursorBuilder(_flatTable)
  280. .setIndexByColumns(_pkCol)
  281. .toIndexCursor();
  282. }
  283. if(!_pkCursor.findFirstRowByEntry(id)) {
  284. throw new IllegalArgumentException("Row with id " + id +
  285. " does not exist");
  286. }
  287. }
  288. private void updateRow(Integer id, Object[] row) throws IOException {
  289. moveToRow(id);
  290. _pkCursor.updateCurrentRow(row);
  291. }
  292. private void deleteRow(Integer id) throws IOException {
  293. moveToRow(id);
  294. _pkCursor.deleteCurrentRow();
  295. }
  296. protected Object[] asRow(Object[] row, V value)
  297. throws IOException
  298. {
  299. int id = value.getId();
  300. _pkCol.setRowValue(row, ((id != INVALID_ID) ? id : Column.AUTO_NUMBER));
  301. int cId = value.getComplexValueForeignKey().get();
  302. _complexValFkCol.setRowValue(
  303. row, ((cId != INVALID_ID) ? cId : Column.AUTO_NUMBER));
  304. return row;
  305. }
  306. private Object[] newRowArray() {
  307. return new Object[_flatTable.getColumnCount()];
  308. }
  309. @Override
  310. public String toString() {
  311. StringBuilder rtn = new StringBuilder();
  312. rtn.append("\n\t\tComplexType: " + getType());
  313. rtn.append("\n\t\tComplexTypeId: " + _complexTypeId);
  314. return rtn.toString();
  315. }
  316. protected static void diffFlatColumns(Table typeObjTable, Table flatTable,
  317. List<Column> typeCols,
  318. List<Column> otherCols)
  319. {
  320. // each "flat"" table has the columns from the "type" table, plus some
  321. // others. separate the "flat" columns into these 2 buckets
  322. for(Column col : flatTable.getColumns()) {
  323. boolean found = false;
  324. try {
  325. typeObjTable.getColumn(col.getName());
  326. found = true;
  327. } catch(IllegalArgumentException e) {
  328. // FIXME better way to test this?
  329. }
  330. if(found) {
  331. typeCols.add(col);
  332. } else {
  333. otherCols.add(col);
  334. }
  335. }
  336. }
  337. public abstract ComplexDataType getType();
  338. protected abstract V toValue(
  339. ComplexValueForeignKey complexValueFk,
  340. Map<String,Object> rawValues)
  341. throws IOException;
  342. protected static abstract class ComplexValueImpl implements ComplexValue
  343. {
  344. private int _id;
  345. private ComplexValueForeignKey _complexValueFk;
  346. protected ComplexValueImpl(int id, ComplexValueForeignKey complexValueFk) {
  347. _id = id;
  348. _complexValueFk = complexValueFk;
  349. }
  350. public int getId() {
  351. return _id;
  352. }
  353. public void setId(int id) {
  354. if(_id != INVALID_ID) {
  355. throw new IllegalStateException("id may not be reset");
  356. }
  357. _id = id;
  358. }
  359. public ComplexValueForeignKey getComplexValueForeignKey() {
  360. return _complexValueFk;
  361. }
  362. public void setComplexValueForeignKey(ComplexValueForeignKey complexValueFk)
  363. {
  364. if(_complexValueFk != INVALID_COMPLEX_VALUE_ID) {
  365. throw new IllegalStateException("complexValueFk may not be reset");
  366. }
  367. _complexValueFk = complexValueFk;
  368. }
  369. public Column getColumn() {
  370. return _complexValueFk.getColumn();
  371. }
  372. @Override
  373. public int hashCode() {
  374. return ((_id * 37) ^ _complexValueFk.hashCode());
  375. }
  376. @Override
  377. public boolean equals(Object o) {
  378. return ((this == o) ||
  379. ((o != null) && (getClass() == o.getClass()) &&
  380. (_id == ((ComplexValueImpl)o)._id) &&
  381. _complexValueFk.equals(((ComplexValueImpl)o)._complexValueFk)));
  382. }
  383. }
  384. }