View Javadoc
1   /*
2   Copyright (c) 2012 Health Market Science, Inc.
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
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   * @author James Ahlborn
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        // apply encoding to file
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 }