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.

CodecHandlerTest.java 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. /*
  2. Copyright (c) 2012 Health Market Science, Inc.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package com.healthmarketscience.jackcess.impl;
  14. import java.io.File;
  15. import java.io.IOException;
  16. import java.io.RandomAccessFile;
  17. import java.nio.ByteBuffer;
  18. import java.nio.channels.FileChannel;
  19. import java.nio.charset.Charset;
  20. import java.util.Iterator;
  21. import com.healthmarketscience.jackcess.ColumnBuilder;
  22. import com.healthmarketscience.jackcess.Cursor;
  23. import com.healthmarketscience.jackcess.DataType;
  24. import com.healthmarketscience.jackcess.Database;
  25. import com.healthmarketscience.jackcess.DatabaseBuilder;
  26. import com.healthmarketscience.jackcess.IndexBuilder;
  27. import com.healthmarketscience.jackcess.Row;
  28. import com.healthmarketscience.jackcess.Table;
  29. import com.healthmarketscience.jackcess.TableBuilder;
  30. import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
  31. import junit.framework.TestCase;
  32. import com.healthmarketscience.jackcess.TestUtil;
  33. /**
  34. *
  35. * @author James Ahlborn
  36. */
  37. public class CodecHandlerTest extends TestCase
  38. {
  39. private static final CodecProvider SIMPLE_PROVIDER = new CodecProvider() {
  40. public CodecHandler createHandler(PageChannel channel, Charset charset)
  41. throws IOException
  42. {
  43. return new SimpleCodecHandler(channel);
  44. }
  45. };
  46. private static final CodecProvider FULL_PROVIDER = new CodecProvider() {
  47. public CodecHandler createHandler(PageChannel channel, Charset charset)
  48. throws IOException
  49. {
  50. return new FullCodecHandler(channel);
  51. }
  52. };
  53. public CodecHandlerTest(String name) throws Exception {
  54. super(name);
  55. }
  56. public void testCodecHandler() throws Exception
  57. {
  58. doTestCodecHandler(true);
  59. doTestCodecHandler(false);
  60. }
  61. private static void doTestCodecHandler(boolean simple) throws Exception
  62. {
  63. for(Database.FileFormat ff : SUPPORTED_FILEFORMATS) {
  64. Database db = TestUtil.createFile(ff);
  65. int pageSize = ((DatabaseImpl)db).getFormat().PAGE_SIZE;
  66. File dbFile = db.getFile();
  67. db.close();
  68. // apply encoding to file
  69. encodeFile(dbFile, pageSize, simple);
  70. db = new DatabaseBuilder(dbFile)
  71. .setCodecProvider(simple ? SIMPLE_PROVIDER : FULL_PROVIDER)
  72. .open();
  73. Table t1 = new TableBuilder("test1")
  74. .addColumn(new ColumnBuilder("id", DataType.LONG).setAutoNumber(true))
  75. .addColumn(new ColumnBuilder("data", DataType.TEXT).setLength(250))
  76. .setPrimaryKey("id")
  77. .addIndex(new IndexBuilder("data_idx").addColumns("data"))
  78. .toTable(db);
  79. Table t2 = new TableBuilder("test2")
  80. .addColumn(new ColumnBuilder("id", DataType.LONG).setAutoNumber(true))
  81. .addColumn(new ColumnBuilder("data", DataType.TEXT).setLength(250))
  82. .setPrimaryKey("id")
  83. .addIndex(new IndexBuilder("data_idx").addColumns("data"))
  84. .toTable(db);
  85. int autonum = 1;
  86. for(int i = 1; i < 2; ++i) {
  87. writeData(t1, t2, autonum, autonum + 100);
  88. autonum += 100;
  89. }
  90. db.close();
  91. }
  92. }
  93. private static void writeData(Table t1, Table t2, int start, int end)
  94. throws Exception
  95. {
  96. Database db = t1.getDatabase();
  97. ((DatabaseImpl)db).getPageChannel().startWrite();
  98. try {
  99. for(int i = start; i < end; ++i) {
  100. t1.addRow(null, "rowdata-" + i + TestUtil.createString(100));
  101. t2.addRow(null, "rowdata-" + i + TestUtil.createString(100));
  102. }
  103. } finally {
  104. ((DatabaseImpl)db).getPageChannel().finishWrite();
  105. }
  106. Cursor c1 = t1.newCursor().setIndex(t1.getPrimaryKeyIndex())
  107. .toCursor();
  108. Cursor c2 = t2.newCursor().setIndex(t2.getPrimaryKeyIndex())
  109. .toCursor();
  110. Iterator<? extends Row> i1 = c1.iterator();
  111. Iterator<? extends Row> i2 = c2.newIterable().reverse().iterator();
  112. int t1rows = 0;
  113. int t2rows = 0;
  114. ((DatabaseImpl)db).getPageChannel().startWrite();
  115. try {
  116. while(i1.hasNext() || i2.hasNext()) {
  117. if(i1.hasNext()) {
  118. checkRow(i1.next());
  119. i1.remove();
  120. ++t1rows;
  121. }
  122. if(i2.hasNext()) {
  123. checkRow(i2.next());
  124. i2.remove();
  125. ++t2rows;
  126. }
  127. }
  128. } finally {
  129. ((DatabaseImpl)db).getPageChannel().finishWrite();
  130. }
  131. assertEquals(100, t1rows);
  132. assertEquals(100, t2rows);
  133. }
  134. private static void checkRow(Row row)
  135. {
  136. int id = row.getInt("id");
  137. String value = row.getString("data");
  138. String valuePrefix = "rowdata-" + id;
  139. assertTrue(value.startsWith(valuePrefix));
  140. assertEquals(valuePrefix.length() + 100, value.length());
  141. }
  142. private static void encodeFile(File dbFile, int pageSize, boolean simple)
  143. throws Exception
  144. {
  145. long dbLen = dbFile.length();
  146. try (RandomAccessFile randomAccessFile = new RandomAccessFile(dbFile, "rw");
  147. FileChannel fileChannel = randomAccessFile.getChannel()) {
  148. ByteBuffer bb = ByteBuffer.allocate(pageSize)
  149. .order(PageChannel.DEFAULT_BYTE_ORDER);
  150. for(long offset = pageSize; offset < dbLen; offset += pageSize) {
  151. bb.clear();
  152. fileChannel.read(bb, offset);
  153. int pageNumber = (int)(offset / pageSize);
  154. if(simple) {
  155. simpleEncode(bb.array(), bb.array(), pageNumber, 0, pageSize);
  156. } else {
  157. fullEncode(bb.array(), bb.array(), pageNumber);
  158. }
  159. bb.rewind();
  160. fileChannel.write(bb, offset);
  161. }
  162. }
  163. }
  164. private static void simpleEncode(byte[] inBuffer, byte[] outBuffer,
  165. int pageNumber, int offset, int limit) {
  166. for(int i = offset; i < limit; ++i) {
  167. int mask = (i + pageNumber) % 256;
  168. outBuffer[i] = (byte)(inBuffer[i] ^ mask);
  169. }
  170. }
  171. private static void simpleDecode(byte[] inBuffer, byte[] outBuffer,
  172. int pageNumber) {
  173. simpleEncode(inBuffer, outBuffer, pageNumber, 0, inBuffer.length);
  174. }
  175. private static void fullEncode(byte[] inBuffer, byte[] outBuffer,
  176. int pageNumber) {
  177. int accum = 0;
  178. for(int i = 0; i < inBuffer.length; ++i) {
  179. int mask = (i + pageNumber + accum) % 256;
  180. accum += inBuffer[i];
  181. outBuffer[i] = (byte)(inBuffer[i] ^ mask);
  182. }
  183. }
  184. private static void fullDecode(byte[] inBuffer, byte[] outBuffer,
  185. int pageNumber) {
  186. int accum = 0;
  187. for(int i = 0; i < inBuffer.length; ++i) {
  188. int mask = (i + pageNumber + accum) % 256;
  189. outBuffer[i] = (byte)(inBuffer[i] ^ mask);
  190. accum += outBuffer[i];
  191. }
  192. }
  193. private static final class SimpleCodecHandler implements CodecHandler
  194. {
  195. private final TempBufferHolder _bufH = TempBufferHolder.newHolder(
  196. TempBufferHolder.Type.HARD, true);
  197. private final PageChannel _channel;
  198. private SimpleCodecHandler(PageChannel channel) {
  199. _channel = channel;
  200. }
  201. public boolean canEncodePartialPage() {
  202. return true;
  203. }
  204. public boolean canDecodeInline() {
  205. return true;
  206. }
  207. public void decodePage(ByteBuffer inPage, ByteBuffer outPage,
  208. int pageNumber)
  209. throws IOException
  210. {
  211. byte[] arr = inPage.array();
  212. simpleDecode(arr, arr, pageNumber);
  213. }
  214. public ByteBuffer encodePage(ByteBuffer page, int pageNumber,
  215. int pageOffset)
  216. throws IOException
  217. {
  218. ByteBuffer bb = _bufH.getPageBuffer(_channel);
  219. bb.clear();
  220. simpleEncode(page.array(), bb.array(), pageNumber, pageOffset,
  221. page.limit());
  222. return bb;
  223. }
  224. }
  225. private static final class FullCodecHandler implements CodecHandler
  226. {
  227. private final TempBufferHolder _bufH = TempBufferHolder.newHolder(
  228. TempBufferHolder.Type.HARD, true);
  229. private final PageChannel _channel;
  230. private FullCodecHandler(PageChannel channel) {
  231. _channel = channel;
  232. }
  233. public boolean canEncodePartialPage() {
  234. return false;
  235. }
  236. public boolean canDecodeInline() {
  237. return true;
  238. }
  239. public void decodePage(ByteBuffer inPage, ByteBuffer outPage,
  240. int pageNumber)
  241. throws IOException
  242. {
  243. byte[] arr = inPage.array();
  244. fullDecode(arr, arr, pageNumber);
  245. }
  246. public ByteBuffer encodePage(ByteBuffer page, int pageNumber,
  247. int pageOffset)
  248. throws IOException
  249. {
  250. assertEquals(0, pageOffset);
  251. assertEquals(_channel.getFormat().PAGE_SIZE, page.limit());
  252. ByteBuffer bb = _bufH.getPageBuffer(_channel);
  253. bb.clear();
  254. fullEncode(page.array(), bb.array(), pageNumber);
  255. return bb;
  256. }
  257. }
  258. }