1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess.impl;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.RandomAccessFile;
22 import java.nio.ByteBuffer;
23 import java.nio.channels.FileChannel;
24 import java.nio.charset.Charset;
25 import java.util.Iterator;
26
27 import com.healthmarketscience.jackcess.ColumnBuilder;
28 import com.healthmarketscience.jackcess.Cursor;
29 import com.healthmarketscience.jackcess.DataType;
30 import com.healthmarketscience.jackcess.Database;
31 import com.healthmarketscience.jackcess.DatabaseBuilder;
32 import com.healthmarketscience.jackcess.IndexBuilder;
33 import com.healthmarketscience.jackcess.Row;
34 import com.healthmarketscience.jackcess.Table;
35 import com.healthmarketscience.jackcess.TableBuilder;
36 import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
37 import junit.framework.TestCase;
38 import com.healthmarketscience.jackcess.TestUtil;
39
40
41
42
43
44 public class CodecHandlerTest extends TestCase
45 {
46 private static final CodecProvider SIMPLE_PROVIDER = new CodecProvider() {
47 public CodecHandler createHandler(PageChannel channel, Charset charset)
48 throws IOException
49 {
50 return new SimpleCodecHandler(channel);
51 }
52 };
53 private static final CodecProvider FULL_PROVIDER = new CodecProvider() {
54 public CodecHandler createHandler(PageChannel channel, Charset charset)
55 throws IOException
56 {
57 return new FullCodecHandler(channel);
58 }
59 };
60
61
62 public CodecHandlerTest(String name) throws Exception {
63 super(name);
64 }
65
66 public void testCodecHandler() throws Exception
67 {
68 doTestCodecHandler(true);
69 doTestCodecHandler(false);
70 }
71
72 private static void doTestCodecHandler(boolean simple) throws Exception
73 {
74 for(Database.FileFormat ff : SUPPORTED_FILEFORMATS) {
75 Database db = TestUtil.createFile(ff);
76 int pageSize = ((DatabaseImpl)db).getFormat().PAGE_SIZE;
77 File dbFile = db.getFile();
78 db.close();
79
80
81 encodeFile(dbFile, pageSize, simple);
82
83 db = new DatabaseBuilder(dbFile)
84 .setCodecProvider(simple ? SIMPLE_PROVIDER : FULL_PROVIDER)
85 .open();
86
87 Table t1 = new TableBuilder("test1")
88 .addColumn(new ColumnBuilder("id", DataType.LONG).setAutoNumber(true))
89 .addColumn(new ColumnBuilder("data", DataType.TEXT).setLength(250))
90 .setPrimaryKey("id")
91 .addIndex(new IndexBuilder("data_idx").addColumns("data"))
92 .toTable(db);
93
94 Table t2 = new TableBuilder("test2")
95 .addColumn(new ColumnBuilder("id", DataType.LONG).setAutoNumber(true))
96 .addColumn(new ColumnBuilder("data", DataType.TEXT).setLength(250))
97 .setPrimaryKey("id")
98 .addIndex(new IndexBuilder("data_idx").addColumns("data"))
99 .toTable(db);
100
101 int autonum = 1;
102 for(int i = 1; i < 2; ++i) {
103 writeData(t1, t2, autonum, autonum + 100);
104 autonum += 100;
105 }
106
107 db.close();
108 }
109 }
110
111 private static void writeData(Table t1, Table t2, int start, int end)
112 throws Exception
113 {
114 Database db = t1.getDatabase();
115 ((DatabaseImpl)db).getPageChannel().startWrite();
116 try {
117 for(int i = start; i < end; ++i) {
118 t1.addRow(null, "rowdata-" + i + TestUtil.createString(100));
119 t2.addRow(null, "rowdata-" + i + TestUtil.createString(100));
120 }
121 } finally {
122 ((DatabaseImpl)db).getPageChannel().finishWrite();
123 }
124
125 Cursor c1 = t1.newCursor().setIndex(t1.getPrimaryKeyIndex())
126 .toCursor();
127 Cursor c2 = t2.newCursor().setIndex(t2.getPrimaryKeyIndex())
128 .toCursor();
129
130 Iterator<? extends Row> i1 = c1.iterator();
131 Iterator<? extends Row> i2 = c2.newIterable().reverse().iterator();
132
133 int t1rows = 0;
134 int t2rows = 0;
135 ((DatabaseImpl)db).getPageChannel().startWrite();
136 try {
137 while(i1.hasNext() || i2.hasNext()) {
138 if(i1.hasNext()) {
139 checkRow(i1.next());
140 i1.remove();
141 ++t1rows;
142 }
143 if(i2.hasNext()) {
144 checkRow(i2.next());
145 i2.remove();
146 ++t2rows;
147 }
148 }
149 } finally {
150 ((DatabaseImpl)db).getPageChannel().finishWrite();
151 }
152
153 assertEquals(100, t1rows);
154 assertEquals(100, t2rows);
155 }
156
157 private static void checkRow(Row row)
158 {
159 int id = row.getInt("id");
160 String value = row.getString("data");
161 String valuePrefix = "rowdata-" + id;
162 assertTrue(value.startsWith(valuePrefix));
163 assertEquals(valuePrefix.length() + 100, value.length());
164 }
165
166 private static void encodeFile(File dbFile, int pageSize, boolean simple)
167 throws Exception
168 {
169 long dbLen = dbFile.length();
170 try (RandomAccessFile randomAccessFile = new RandomAccessFile(dbFile, "rw");
171 FileChannel fileChannel = randomAccessFile.getChannel()) {
172 ByteBuffer bb = ByteBuffer.allocate(pageSize)
173 .order(PageChannel.DEFAULT_BYTE_ORDER);
174 for(long offset = pageSize; offset < dbLen; offset += pageSize) {
175
176 bb.clear();
177 fileChannel.read(bb, offset);
178
179 int pageNumber = (int)(offset / pageSize);
180 if(simple) {
181 simpleEncode(bb.array(), bb.array(), pageNumber, 0, pageSize);
182 } else {
183 fullEncode(bb.array(), bb.array(), pageNumber);
184 }
185
186 bb.rewind();
187 fileChannel.write(bb, offset);
188 }
189 }
190 }
191
192 private static void simpleEncode(byte[] inBuffer, byte[] outBuffer,
193 int pageNumber, int offset, int limit) {
194 for(int i = offset; i < limit; ++i) {
195 int mask = (i + pageNumber) % 256;
196 outBuffer[i] = (byte)(inBuffer[i] ^ mask);
197 }
198 }
199
200 private static void simpleDecode(byte[] inBuffer, byte[] outBuffer,
201 int pageNumber) {
202 simpleEncode(inBuffer, outBuffer, pageNumber, 0, inBuffer.length);
203 }
204
205 private static void fullEncode(byte[] inBuffer, byte[] outBuffer,
206 int pageNumber) {
207 int accum = 0;
208 for(int i = 0; i < inBuffer.length; ++i) {
209 int mask = (i + pageNumber + accum) % 256;
210 accum += inBuffer[i];
211 outBuffer[i] = (byte)(inBuffer[i] ^ mask);
212 }
213 }
214
215 private static void fullDecode(byte[] inBuffer, byte[] outBuffer,
216 int pageNumber) {
217 int accum = 0;
218 for(int i = 0; i < inBuffer.length; ++i) {
219 int mask = (i + pageNumber + accum) % 256;
220 outBuffer[i] = (byte)(inBuffer[i] ^ mask);
221 accum += outBuffer[i];
222 }
223 }
224
225 private static final class SimpleCodecHandler implements CodecHandler
226 {
227 private final TempBufferHolder _bufH = TempBufferHolder.newHolder(
228 TempBufferHolder.Type.HARD, true);
229 private final PageChannel _channel;
230
231 private SimpleCodecHandler(PageChannel channel) {
232 _channel = channel;
233 }
234
235 public boolean canEncodePartialPage() {
236 return true;
237 }
238
239 public boolean canDecodeInline() {
240 return true;
241 }
242
243 public void decodePage(ByteBuffer inPage, ByteBuffer outPage,
244 int pageNumber)
245 throws IOException
246 {
247 byte[] arr = inPage.array();
248 simpleDecode(arr, arr, pageNumber);
249 }
250
251 public ByteBuffer encodePage(ByteBuffer page, int pageNumber,
252 int pageOffset)
253 throws IOException
254 {
255 ByteBuffer bb = _bufH.getPageBuffer(_channel);
256 bb.clear();
257 simpleEncode(page.array(), bb.array(), pageNumber, pageOffset,
258 page.limit());
259 return bb;
260 }
261 }
262
263 private static final class FullCodecHandler implements CodecHandler
264 {
265 private final TempBufferHolder _bufH = TempBufferHolder.newHolder(
266 TempBufferHolder.Type.HARD, true);
267 private final PageChannel _channel;
268
269 private FullCodecHandler(PageChannel channel) {
270 _channel = channel;
271 }
272
273 public boolean canEncodePartialPage() {
274 return false;
275 }
276
277 public boolean canDecodeInline() {
278 return true;
279 }
280
281 public void decodePage(ByteBuffer inPage, ByteBuffer outPage,
282 int pageNumber)
283 throws IOException
284 {
285 byte[] arr = inPage.array();
286 fullDecode(arr, arr, pageNumber);
287 }
288
289 public ByteBuffer encodePage(ByteBuffer page, int pageNumber,
290 int pageOffset)
291 throws IOException
292 {
293 assertEquals(0, pageOffset);
294 assertEquals(_channel.getFormat().PAGE_SIZE, page.limit());
295
296 ByteBuffer bb = _bufH.getPageBuffer(_channel);
297 bb.clear();
298 fullEncode(page.array(), bb.array(), pageNumber);
299 return bb;
300 }
301 }
302
303 }