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.

Table.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. /*
  2. Copyright (c) 2005 Health Market Science, Inc.
  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. You can contact Health Market Science at info@healthmarketscience.com
  16. or at the following address:
  17. Health Market Science
  18. 2700 Horizon Drive
  19. Suite 200
  20. King of Prussia, PA 19406
  21. */
  22. package com.healthmarketscience.jackcess;
  23. import java.io.IOException;
  24. import java.nio.ByteBuffer;
  25. import java.nio.ByteOrder;
  26. import java.sql.SQLException;
  27. import java.util.ArrayList;
  28. import java.util.Arrays;
  29. import java.util.Collection;
  30. import java.util.Collections;
  31. import java.util.Iterator;
  32. import java.util.LinkedHashMap;
  33. import java.util.List;
  34. import java.util.Map;
  35. import org.apache.commons.logging.Log;
  36. import org.apache.commons.logging.LogFactory;
  37. /**
  38. * A single database table
  39. * @author Tim McCune
  40. */
  41. public class Table {
  42. private static final Log LOG = LogFactory.getLog(Table.class);
  43. /** Table type code for system tables */
  44. public static final byte TYPE_SYSTEM = 0x53;
  45. /** Table type code for user tables */
  46. public static final byte TYPE_USER = 0x4e;
  47. /** Buffer used for reading the table */
  48. private ByteBuffer _buffer;
  49. /** Type of the table (either TYPE_SYSTEM or TYPE_USER) */
  50. private byte _tableType;
  51. /** Number of the current row in a data page */
  52. private int _currentRowInPage;
  53. /** Number of indexes on the table */
  54. private int _indexCount;
  55. /** Offset index in the buffer where the last row read started */
  56. private short _lastRowStart;
  57. /** Number of rows in the table */
  58. private int _rowCount;
  59. private int _tableDefPageNumber;
  60. /** Number of rows left to be read on the current page */
  61. private short _rowsLeftOnPage = 0;
  62. /** Offset index in the buffer of the start of the current row */
  63. private short _rowStart;
  64. /** Number of columns in the table */
  65. private short _columnCount;
  66. /** Format of the database that contains this table */
  67. private JetFormat _format;
  68. /** List of columns in this table */
  69. private List<Column> _columns = new ArrayList<Column>();
  70. /** List of indexes on this table */
  71. private List<Index> _indexes = new ArrayList<Index>();
  72. /** Used to read in pages */
  73. private PageChannel _pageChannel;
  74. /** Usage map of pages that this table owns */
  75. private UsageMap _ownedPages;
  76. /** Usage map of pages that this table owns with free space on them */
  77. private UsageMap _freeSpacePages;
  78. /**
  79. * Only used by unit tests
  80. */
  81. Table() throws IOException {
  82. _pageChannel = new PageChannel(null, JetFormat.VERSION_4);
  83. }
  84. /**
  85. * @param buffer Buffer to read the table with
  86. * @param pageChannel Page channel to get database pages from
  87. * @param format Format of the database that contains this table
  88. * @param pageNumber Page number of the table definition
  89. */
  90. protected Table(ByteBuffer buffer, PageChannel pageChannel, JetFormat format, int pageNumber)
  91. throws IOException, SQLException
  92. {
  93. _buffer = buffer;
  94. _pageChannel = pageChannel;
  95. _format = format;
  96. _tableDefPageNumber = pageNumber;
  97. int nextPage;
  98. ByteBuffer nextPageBuffer = null;
  99. nextPage = _buffer.getInt(_format.OFFSET_NEXT_TABLE_DEF_PAGE);
  100. while (nextPage != 0) {
  101. if (nextPageBuffer == null) {
  102. nextPageBuffer = ByteBuffer.allocate(format.PAGE_SIZE);
  103. nextPageBuffer.order(ByteOrder.LITTLE_ENDIAN);
  104. }
  105. _pageChannel.readPage(nextPageBuffer, nextPage);
  106. nextPage = nextPageBuffer.getInt(_format.OFFSET_NEXT_TABLE_DEF_PAGE);
  107. ByteBuffer newBuffer = ByteBuffer.allocate(_buffer.capacity() + format.PAGE_SIZE - 8);
  108. newBuffer.order(ByteOrder.LITTLE_ENDIAN);
  109. newBuffer.put(_buffer);
  110. newBuffer.put(nextPageBuffer.array(), 8, format.PAGE_SIZE - 8);
  111. _buffer = newBuffer;
  112. }
  113. readPage();
  114. }
  115. /**
  116. * @return All of the columns in this table (unmodifiable List)
  117. */
  118. public List<Column> getColumns() {
  119. return Collections.unmodifiableList(_columns);
  120. }
  121. /**
  122. * Only called by unit tests
  123. */
  124. void setColumns(List<Column> columns) {
  125. _columns = columns;
  126. }
  127. /**
  128. * @return All of the Indexes on this table (unmodifiable List)
  129. */
  130. public List<Index> getIndexes() {
  131. return Collections.unmodifiableList(_indexes);
  132. }
  133. /**
  134. * After calling this method, getNextRow will return the first row in the table
  135. */
  136. public void reset() {
  137. _rowsLeftOnPage = 0;
  138. _ownedPages.reset();
  139. _currentRowInPage = 0;
  140. }
  141. /**
  142. * @return The next row in this table (Column name -> Column value)
  143. */
  144. public Map<String, Object> getNextRow() throws IOException {
  145. return getNextRow(null);
  146. }
  147. /**
  148. * Delete the current row (retrieved by a call to {@link #getNextRow}).
  149. */
  150. public void deleteCurrentRow() throws IOException {
  151. if (_currentRowInPage == 0) {
  152. throw new IllegalStateException("Must call getNextRow first");
  153. }
  154. int index = _format.OFFSET_DATA_ROW_LOCATION_BLOCK + (_currentRowInPage - 1) *
  155. _format.SIZE_ROW_LOCATION + 1;
  156. _buffer.put(index, (byte) (_buffer.get(index) | 0xc0));
  157. _pageChannel.writePage(_buffer, _ownedPages.getCurrentPageNumber());
  158. }
  159. /**
  160. * @param columnNames Only column names in this collection will be returned
  161. * @return The next row in this table (Column name -> Column value)
  162. */
  163. public Map<String, Object> getNextRow(Collection<String> columnNames)
  164. throws IOException
  165. {
  166. if (!positionAtNextRow()) {
  167. return null;
  168. }
  169. if (LOG.isDebugEnabled()) {
  170. LOG.debug("Data block at position " + Integer.toHexString(_buffer.position()) +
  171. ":\n" + ByteUtil.toHexString(_buffer, _buffer.position(),
  172. _buffer.limit() - _buffer.position()));
  173. }
  174. short columnCount = _buffer.getShort(); //Number of columns in this table
  175. Map<String, Object> rtn = new LinkedHashMap<String, Object>(columnCount);
  176. NullMask nullMask = new NullMask(columnCount);
  177. _buffer.position(_buffer.limit() - nullMask.byteSize()); //Null mask at end
  178. nullMask.read(_buffer);
  179. _buffer.position(_buffer.limit() - nullMask.byteSize() - 2);
  180. short varColumnCount = _buffer.getShort(); //Number of variable length columns
  181. byte[][] varColumnData = new byte[varColumnCount][]; //Holds variable length column data
  182. //Read in the offsets of each of the variable length columns
  183. short[] varColumnOffsets = new short[varColumnCount];
  184. _buffer.position(_buffer.position() - 2 - (varColumnCount * 2) - 2);
  185. short lastVarColumnStart = _buffer.getShort();
  186. for (short i = 0; i < varColumnCount; i++) {
  187. varColumnOffsets[i] = _buffer.getShort();
  188. }
  189. //Read in the actual data for each of the variable length columns
  190. for (short i = 0; i < varColumnCount; i++) {
  191. _buffer.position(_rowStart + varColumnOffsets[i]);
  192. varColumnData[i] = new byte[lastVarColumnStart - varColumnOffsets[i]];
  193. _buffer.get(varColumnData[i]);
  194. lastVarColumnStart = varColumnOffsets[i];
  195. }
  196. int columnNumber = 0;
  197. int varColumnDataIndex = varColumnCount - 1;
  198. _buffer.position(_rowStart + 2); //Move back to the front of the buffer
  199. //Now read in the fixed length columns and populate the columnData array
  200. //with the combination of fixed length and variable length data.
  201. byte[] columnData = null;
  202. for (Iterator iter = _columns.iterator(); iter.hasNext(); columnNumber++) {
  203. Column column = (Column) iter.next();
  204. boolean isNull = nullMask.isNull(columnNumber);
  205. Object value = null;
  206. if (column.getType() == DataType.BOOLEAN) {
  207. value = new Boolean(!isNull); //Boolean values are stored in the null mask
  208. } else {
  209. if (!column.isVariableLength())
  210. {
  211. //Read in fixed length column data
  212. columnData = new byte[column.getLength()];
  213. _buffer.get(columnData);
  214. }
  215. else
  216. {
  217. if (!isNull)
  218. {
  219. //Refer to already-read-in variable length data
  220. columnData = varColumnData[varColumnDataIndex];
  221. }
  222. --varColumnDataIndex;
  223. }
  224. if (!isNull && columnData != null &&
  225. (columnNames == null || columnNames.contains(column.getName())))
  226. {
  227. //Add the value if we are interested in it.
  228. value = column.read(columnData);
  229. }
  230. }
  231. rtn.put(column.getName(), value);
  232. }
  233. return rtn;
  234. }
  235. /**
  236. * Position the buffer at the next row in the table
  237. * @return True if another row was found, false if there are no more rows
  238. */
  239. private boolean positionAtNextRow() throws IOException {
  240. if (_rowsLeftOnPage == 0) {
  241. do {
  242. if (!_ownedPages.getNextPage(_buffer)) {
  243. //No more owned pages. No more rows.
  244. return false;
  245. }
  246. } while (_buffer.get() != PageTypes.DATA); //Only interested in data pages
  247. _rowsLeftOnPage = _buffer.getShort(_format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
  248. _currentRowInPage = 0;
  249. _lastRowStart = (short) _format.PAGE_SIZE;
  250. }
  251. _rowStart = _buffer.getShort(_format.OFFSET_DATA_ROW_LOCATION_BLOCK +
  252. _currentRowInPage * _format.SIZE_ROW_LOCATION);
  253. _currentRowInPage++;
  254. _rowsLeftOnPage--;
  255. if (_rowStart < 0) {
  256. // Deleted row. Skip.
  257. return positionAtNextRow();
  258. } else if ((_rowStart & 0x4000) > 0) {
  259. // Overflow page.
  260. // FIXME - Currently skipping this. Need to figure out how to read it.
  261. _buffer.position(_rowStart - 0x4000);
  262. int overflow = _buffer.getInt();
  263. _lastRowStart -= 4;
  264. return positionAtNextRow();
  265. } else {
  266. _buffer.position(_rowStart);
  267. _buffer.limit(_lastRowStart);
  268. _lastRowStart = _rowStart;
  269. return true;
  270. }
  271. }
  272. /**
  273. * Read the table definition
  274. */
  275. private void readPage() throws IOException, SQLException {
  276. if (LOG.isDebugEnabled()) {
  277. _buffer.rewind();
  278. LOG.debug("Table def block:\n" + ByteUtil.toHexString(_buffer,
  279. _format.SIZE_TDEF_BLOCK));
  280. }
  281. _rowCount = _buffer.getInt(_format.OFFSET_NUM_ROWS);
  282. _tableType = _buffer.get(_format.OFFSET_TABLE_TYPE);
  283. _columnCount = _buffer.getShort(_format.OFFSET_NUM_COLS);
  284. _indexCount = _buffer.getInt(_format.OFFSET_NUM_INDEXES);
  285. byte rowNum = _buffer.get(_format.OFFSET_OWNED_PAGES);
  286. int pageNum = ByteUtil.get3ByteInt(_buffer, _format.OFFSET_OWNED_PAGES + 1);
  287. _ownedPages = UsageMap.read(_pageChannel, pageNum, rowNum, _format);
  288. rowNum = _buffer.get(_format.OFFSET_FREE_SPACE_PAGES);
  289. pageNum = ByteUtil.get3ByteInt(_buffer, _format.OFFSET_FREE_SPACE_PAGES + 1);
  290. _freeSpacePages = UsageMap.read(_pageChannel, pageNum, rowNum, _format);
  291. for (int i = 0; i < _indexCount; i++) {
  292. Index index = new Index(_tableDefPageNumber, _pageChannel, _format);
  293. _indexes.add(index);
  294. index.setRowCount(_buffer.getInt(_format.OFFSET_INDEX_DEF_BLOCK +
  295. i * _format.SIZE_INDEX_DEFINITION + 4));
  296. }
  297. int offset = _format.OFFSET_INDEX_DEF_BLOCK +
  298. _indexCount * _format.SIZE_INDEX_DEFINITION;
  299. Column column;
  300. for (int i = 0; i < _columnCount; i++) {
  301. column = new Column(_buffer,
  302. offset + i * _format.SIZE_COLUMN_HEADER, _pageChannel, _format);
  303. _columns.add(column);
  304. }
  305. offset += _columnCount * _format.SIZE_COLUMN_HEADER;
  306. for (int i = 0; i < _columnCount; i++) {
  307. column = (Column) _columns.get(i);
  308. short nameLength = _buffer.getShort(offset);
  309. offset += 2;
  310. byte[] nameBytes = new byte[nameLength];
  311. _buffer.position(offset);
  312. _buffer.get(nameBytes, 0, (int) nameLength);
  313. column.setName(_format.CHARSET.decode(ByteBuffer.wrap(nameBytes)).toString());
  314. offset += nameLength;
  315. }
  316. Collections.sort(_columns);
  317. for (int i = 0; i < _indexCount; i++) {
  318. _buffer.getInt(); //Forward past Unknown
  319. ((Index) _indexes.get(i)).read(_buffer, _columns);
  320. }
  321. for (int i = 0; i < _indexCount; i++) {
  322. _buffer.getInt(); //Forward past Unknown
  323. ((Index) _indexes.get(i)).setIndexNumber(_buffer.getInt());
  324. _buffer.position(_buffer.position() + 20);
  325. }
  326. Collections.sort(_indexes);
  327. for (int i = 0; i < _indexCount; i++) {
  328. byte[] nameBytes = new byte[_buffer.getShort()];
  329. _buffer.get(nameBytes);
  330. ((Index) _indexes.get(i)).setName(_format.CHARSET.decode(ByteBuffer.wrap(
  331. nameBytes)).toString());
  332. }
  333. }
  334. /**
  335. * Add a single row to this table and write it to disk
  336. */
  337. public void addRow(Object[] row) throws IOException {
  338. addRows(Collections.singletonList(row));
  339. }
  340. /**
  341. * Add multiple rows to this table, only writing to disk after all
  342. * rows have been written, and every time a data page is filled. This
  343. * is much more efficient than calling <code>addRow</code> multiple times.
  344. * @param rows List of Object[] row values
  345. */
  346. public void addRows(List<? extends Object[]> rows) throws IOException {
  347. ByteBuffer dataPage = _pageChannel.createPageBuffer();
  348. ByteBuffer[] rowData = new ByteBuffer[rows.size()];
  349. Iterator<? extends Object[]> iter = rows.iterator();
  350. for (int i = 0; iter.hasNext(); i++) {
  351. rowData[i] = createRow((Object[]) iter.next());
  352. }
  353. List<Integer> pageNumbers = _ownedPages.getPageNumbers();
  354. int pageNumber;
  355. int rowSize;
  356. if (pageNumbers.size() == 0) {
  357. //No data pages exist. Create a new one.
  358. pageNumber = newDataPage(dataPage, rowData[0]);
  359. } else {
  360. //Get the last data page.
  361. //Not bothering to check other pages for free space.
  362. pageNumber = ((Integer) pageNumbers.get(pageNumbers.size() - 1)).intValue();
  363. _pageChannel.readPage(dataPage, pageNumber);
  364. }
  365. for (int i = 0; i < rowData.length; i++) {
  366. rowSize = rowData[i].limit();
  367. short freeSpaceInPage = dataPage.getShort(_format.OFFSET_FREE_SPACE);
  368. if (freeSpaceInPage < (rowSize + _format.SIZE_ROW_LOCATION)) {
  369. //Last data page is full. Create a new one.
  370. if (rowSize + _format.SIZE_ROW_LOCATION > _format.MAX_ROW_SIZE) {
  371. throw new IOException("Row size " + rowSize + " is too large");
  372. }
  373. _pageChannel.writePage(dataPage, pageNumber);
  374. dataPage.clear();
  375. pageNumber = newDataPage(dataPage, rowData[i]);
  376. _freeSpacePages.removePageNumber(pageNumber);
  377. freeSpaceInPage = dataPage.getShort(_format.OFFSET_FREE_SPACE);
  378. }
  379. //Decrease free space record.
  380. dataPage.putShort(_format.OFFSET_FREE_SPACE, (short) (freeSpaceInPage -
  381. rowSize - _format.SIZE_ROW_LOCATION));
  382. //Increment row count record.
  383. short rowCount = dataPage.getShort(_format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
  384. dataPage.putShort(_format.OFFSET_NUM_ROWS_ON_DATA_PAGE, (short) (rowCount + 1));
  385. short rowLocation = (short) _format.PAGE_SIZE;
  386. if (rowCount > 0) {
  387. rowLocation = dataPage.getShort(_format.OFFSET_DATA_ROW_LOCATION_BLOCK +
  388. (rowCount - 1) * _format.SIZE_ROW_LOCATION);
  389. if (rowLocation < 0) {
  390. // Deleted row
  391. rowLocation &= ~0xc000;
  392. }
  393. }
  394. rowLocation -= rowSize;
  395. dataPage.putShort(_format.OFFSET_DATA_ROW_LOCATION_BLOCK +
  396. rowCount * _format.SIZE_ROW_LOCATION, rowLocation);
  397. dataPage.position(rowLocation);
  398. dataPage.put(rowData[i]);
  399. Iterator<Index> indIter = _indexes.iterator();
  400. while (indIter.hasNext()) {
  401. Index index = (Index) indIter.next();
  402. index.addRow((Object[]) rows.get(i), pageNumber, (byte) rowCount);
  403. }
  404. }
  405. _pageChannel.writePage(dataPage, pageNumber);
  406. //Update tdef page
  407. ByteBuffer tdefPage = _pageChannel.createPageBuffer();
  408. _pageChannel.readPage(tdefPage, _tableDefPageNumber);
  409. tdefPage.putInt(_format.OFFSET_NUM_ROWS, ++_rowCount);
  410. Iterator<Index> indIter = _indexes.iterator();
  411. for (int i = 0; i < _indexes.size(); i++) {
  412. tdefPage.putInt(_format.OFFSET_INDEX_DEF_BLOCK +
  413. i * _format.SIZE_INDEX_DEFINITION + 4, _rowCount);
  414. Index index = (Index) indIter.next();
  415. index.update();
  416. }
  417. _pageChannel.writePage(tdefPage, _tableDefPageNumber);
  418. }
  419. /**
  420. * Create a new data page
  421. * @return Page number of the new page
  422. */
  423. private int newDataPage(ByteBuffer dataPage, ByteBuffer rowData) throws IOException {
  424. if (LOG.isDebugEnabled()) {
  425. LOG.debug("Creating new data page");
  426. }
  427. dataPage.put(PageTypes.DATA); //Page type
  428. dataPage.put((byte) 1); //Unknown
  429. dataPage.putShort((short) (_format.PAGE_SIZE - _format.OFFSET_DATA_ROW_LOCATION_BLOCK -
  430. (rowData.limit() - 1) - _format.SIZE_ROW_LOCATION)); //Free space in this page
  431. dataPage.putInt(_tableDefPageNumber); //Page pointer to table definition
  432. dataPage.putInt(0); //Unknown
  433. dataPage.putInt(0); //Number of records on this page
  434. int pageNumber = _pageChannel.writeNewPage(dataPage);
  435. _ownedPages.addPageNumber(pageNumber);
  436. _freeSpacePages.addPageNumber(pageNumber);
  437. return pageNumber;
  438. }
  439. /**
  440. * Serialize a row of Objects into a byte buffer
  441. */
  442. ByteBuffer createRow(Object[] rowArray) throws IOException {
  443. ByteBuffer buffer = _pageChannel.createPageBuffer();
  444. buffer.putShort((short) _columns.size());
  445. NullMask nullMask = new NullMask(_columns.size());
  446. Iterator iter;
  447. int index = 0;
  448. Column col;
  449. List<Object> row = new ArrayList<Object>(Arrays.asList(rowArray));
  450. //Append null for arrays that are too small
  451. for (int i = rowArray.length; i < _columnCount; i++) {
  452. row.add(null);
  453. }
  454. for (iter = _columns.iterator(); iter.hasNext() && index < row.size(); index++) {
  455. col = (Column) iter.next();
  456. if (!col.isVariableLength()) {
  457. //Fixed length column data comes first
  458. buffer.put(col.write(row.get(index)));
  459. }
  460. if (col.getType() == DataType.BOOLEAN) {
  461. if (row.get(index) != null) {
  462. if (!((Boolean) row.get(index)).booleanValue()) {
  463. //Booleans are stored in the null mask
  464. nullMask.markNull(index);
  465. }
  466. }
  467. } else if (row.get(index) == null) {
  468. nullMask.markNull(index);
  469. }
  470. }
  471. int varLengthCount = Column.countVariableLength(_columns);
  472. short[] varColumnOffsets = new short[varLengthCount];
  473. index = 0;
  474. int varColumnOffsetsIndex = 0;
  475. //Now write out variable length column data
  476. for (iter = _columns.iterator(); iter.hasNext() && index < row.size(); index++) {
  477. col = (Column) iter.next();
  478. short offset = (short) buffer.position();
  479. if (col.isVariableLength()) {
  480. if (row.get(index) != null) {
  481. buffer.put(col.write(row.get(index)));
  482. }
  483. varColumnOffsets[varColumnOffsetsIndex++] = offset;
  484. }
  485. }
  486. buffer.putShort((short) buffer.position()); //EOD marker
  487. //Now write out variable length offsets
  488. //Offsets are stored in reverse order
  489. for (int i = varColumnOffsets.length - 1; i >= 0; i--) {
  490. buffer.putShort(varColumnOffsets[i]);
  491. }
  492. buffer.putShort((short) varLengthCount); //Number of var length columns
  493. buffer.put(nullMask.wrap()); //Null mask
  494. buffer.limit(buffer.position());
  495. buffer.flip();
  496. if (LOG.isDebugEnabled()) {
  497. LOG.debug("Creating new data block:\n" + ByteUtil.toHexString(buffer, buffer.limit()));
  498. }
  499. return buffer;
  500. }
  501. public String toString() {
  502. StringBuilder rtn = new StringBuilder();
  503. rtn.append("Type: " + _tableType);
  504. rtn.append("\nRow count: " + _rowCount);
  505. rtn.append("\nColumn count: " + _columnCount);
  506. rtn.append("\nIndex count: " + _indexCount);
  507. rtn.append("\nColumns:\n");
  508. Iterator iter = _columns.iterator();
  509. while (iter.hasNext()) {
  510. rtn.append(iter.next().toString());
  511. }
  512. rtn.append("\nIndexes:\n");
  513. iter = _indexes.iterator();
  514. while (iter.hasNext()) {
  515. rtn.append(iter.next().toString());
  516. }
  517. rtn.append("\nOwned pages: " + _ownedPages + "\n");
  518. return rtn.toString();
  519. }
  520. /**
  521. * @return A simple String representation of the entire table in tab-delimited format
  522. */
  523. public String display() throws IOException {
  524. return display(Long.MAX_VALUE);
  525. }
  526. /**
  527. * @param limit Maximum number of rows to display
  528. * @return A simple String representation of the entire table in tab-delimited format
  529. */
  530. public String display(long limit) throws IOException {
  531. reset();
  532. StringBuilder rtn = new StringBuilder();
  533. Iterator iter = _columns.iterator();
  534. while (iter.hasNext()) {
  535. Column col = (Column) iter.next();
  536. rtn.append(col.getName());
  537. if (iter.hasNext()) {
  538. rtn.append("\t");
  539. }
  540. }
  541. rtn.append("\n");
  542. Map row;
  543. int rowCount = 0;
  544. while ((rowCount++ < limit) && (row = getNextRow()) != null) {
  545. iter = row.values().iterator();
  546. while (iter.hasNext()) {
  547. Object obj = iter.next();
  548. if (obj instanceof byte[]) {
  549. byte[] b = (byte[]) obj;
  550. rtn.append(ByteUtil.toHexString(ByteBuffer.wrap(b), b.length));
  551. //This block can be used to easily dump a binary column to a file
  552. /*java.io.File f = java.io.File.createTempFile("ole", ".bin");
  553. java.io.FileOutputStream out = new java.io.FileOutputStream(f);
  554. out.write(b);
  555. out.flush();
  556. out.close();*/
  557. } else {
  558. rtn.append(String.valueOf(obj));
  559. }
  560. if (iter.hasNext()) {
  561. rtn.append("\t");
  562. }
  563. }
  564. rtn.append("\n");
  565. }
  566. return rtn.toString();
  567. }
  568. }