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.create(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     FileChannel fileChannel = new RandomAccessFile(dbFile, "rw").getChannel();
171     ByteBuffer bb = ByteBuffer.allocate(pageSize)
172       .order(PageChannel.DEFAULT_BYTE_ORDER);
173     for(long offset = pageSize; offset < dbLen; offset += pageSize) {
174 
175       bb.clear();
176       fileChannel.read(bb, offset);
177       
178       int pageNumber = (int)(offset / pageSize);
179       if(simple) {
180         simpleEncode(bb.array(), bb.array(), pageNumber, 0, pageSize);
181       } else {
182         fullEncode(bb.array(), bb.array(), pageNumber);
183       }
184 
185       bb.rewind();
186       fileChannel.write(bb, offset);
187     }
188     fileChannel.close();
189   }
190 
191   private static void simpleEncode(byte[] inBuffer, byte[] outBuffer,
192                                    int pageNumber, int offset, int limit) {
193     for(int i = offset; i < limit; ++i) {
194       int mask = (i + pageNumber) % 256;
195       outBuffer[i] = (byte)(inBuffer[i] ^ mask);
196     }
197   }
198 
199   private static void simpleDecode(byte[] inBuffer, byte[] outBuffer,
200                                    int pageNumber) {
201     simpleEncode(inBuffer, outBuffer, pageNumber, 0, inBuffer.length);
202   }
203 
204   private static void fullEncode(byte[] inBuffer, byte[] outBuffer,
205                                  int pageNumber) {
206     int accum = 0;
207     for(int i = 0; i < inBuffer.length; ++i) {
208       int mask = (i + pageNumber + accum) % 256;
209       accum += inBuffer[i];
210       outBuffer[i] = (byte)(inBuffer[i] ^ mask);
211     }
212   }
213 
214   private static void fullDecode(byte[] inBuffer, byte[] outBuffer,
215                                  int pageNumber) {
216     int accum = 0;
217     for(int i = 0; i < inBuffer.length; ++i) {
218       int mask = (i + pageNumber + accum) % 256;
219       outBuffer[i] = (byte)(inBuffer[i] ^ mask);
220       accum += outBuffer[i];
221     }
222   }
223 
224   private static final class SimpleCodecHandler implements CodecHandler 
225   {
226     private final TempBufferHolder _bufH = TempBufferHolder.newHolder(
227         TempBufferHolder.Type.HARD, true);
228     private final PageChannel _channel;
229     
230     private SimpleCodecHandler(PageChannel channel) {
231       _channel = channel;
232     }
233 
234     public boolean canEncodePartialPage() {
235       return true;
236     }
237 
238     public boolean canDecodeInline() {
239       return true;
240     }
241     
242     public void decodePage(ByteBuffer inPage, ByteBuffer outPage,
243                            int pageNumber) 
244       throws IOException 
245     {
246       byte[] arr = inPage.array();
247       simpleDecode(arr, arr, pageNumber);
248     }
249 
250     public ByteBuffer encodePage(ByteBuffer page, int pageNumber,
251                                  int pageOffset) 
252       throws IOException
253     {
254       ByteBuffer bb = _bufH.getPageBuffer(_channel);
255       bb.clear();
256       simpleEncode(page.array(), bb.array(), pageNumber, pageOffset, 
257                    page.limit());
258       return bb;
259     }
260   }
261 
262   private static final class FullCodecHandler implements CodecHandler 
263   {
264     private final TempBufferHolder _bufH = TempBufferHolder.newHolder(
265         TempBufferHolder.Type.HARD, true);
266     private final PageChannel _channel;
267     
268     private FullCodecHandler(PageChannel channel) {
269       _channel = channel;
270     }
271     
272     public boolean canEncodePartialPage() {
273       return false;
274     }
275 
276     public boolean canDecodeInline() {
277       return true;
278     }
279     
280     public void decodePage(ByteBuffer inPage, ByteBuffer outPage, 
281                            int pageNumber) 
282       throws IOException 
283     {
284       byte[] arr = inPage.array();
285       fullDecode(arr, arr, pageNumber);
286     }
287 
288     public ByteBuffer encodePage(ByteBuffer page, int pageNumber, 
289                                  int pageOffset) 
290       throws IOException
291     {
292       assertEquals(0, pageOffset);
293       assertEquals(_channel.getFormat().PAGE_SIZE, page.limit());
294 
295       ByteBuffer bb = _bufH.getPageBuffer(_channel);
296       bb.clear();
297       fullEncode(page.array(), bb.array(), pageNumber);
298       return bb;
299     }
300   }
301 
302 }