View Javadoc
1   /*
2   Copyright (c) 2015 James Ahlborn
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;
18  
19  import java.io.DataInputStream;
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.io.FileOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.io.PrintWriter;
27  import java.lang.reflect.Field;
28  import java.nio.ByteBuffer;
29  import java.nio.channels.FileChannel;
30  import java.nio.charset.Charset;
31  import java.time.Instant;
32  import java.time.LocalDateTime;
33  import java.time.ZoneId;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.Calendar;
37  import java.util.Date;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.TimeZone;
41  
42  import static com.healthmarketscience.jackcess.Database.*;
43  import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
44  import com.healthmarketscience.jackcess.impl.ByteUtil;
45  import com.healthmarketscience.jackcess.impl.DatabaseImpl;
46  import com.healthmarketscience.jackcess.impl.IndexData;
47  import com.healthmarketscience.jackcess.impl.IndexImpl;
48  import com.healthmarketscience.jackcess.impl.JetFormatTest;
49  import com.healthmarketscience.jackcess.impl.JetFormatTest.TestDB;
50  import com.healthmarketscience.jackcess.impl.RowIdImpl;
51  import com.healthmarketscience.jackcess.impl.RowImpl;
52  import com.healthmarketscience.jackcess.util.MemFileChannel;
53  import org.junit.Assert;
54  
55  /**
56   * Utilty code for the test cases.
57   *
58   * @author James Ahlborn
59   */
60  @SuppressWarnings("deprecation")
61  public class TestUtil
62  {
63    public static final TimeZone TEST_TZ =
64      TimeZone.getTimeZone("America/New_York");
65  
66    private static final ThreadLocal<Boolean> _autoSync =
67      new ThreadLocal<Boolean>();
68  
69    private TestUtil() {}
70  
71    static void setTestAutoSync(boolean autoSync) {
72      _autoSync.set(autoSync);
73    }
74  
75    static void clearTestAutoSync() {
76      _autoSync.remove();
77    }
78  
79    static boolean getTestAutoSync() {
80      Boolean autoSync = _autoSync.get();
81      return ((autoSync != null) ? autoSync : Database.DEFAULT_AUTO_SYNC);
82    }
83  
84    public static Database open(FileFormat fileFormat, File file)
85      throws Exception
86    {
87      return open(fileFormat, file, false);
88    }
89  
90    public static Database open(FileFormat fileFormat, File file, boolean inMem)
91      throws Exception
92    {
93      return open(fileFormat, file, inMem, null);
94    }
95  
96    public static Database open(FileFormat fileFormat, File file, boolean inMem,
97                                Charset charset)
98      throws Exception
99    {
100     return openDB(fileFormat, file, inMem, charset, true);
101   }
102 
103   public static Database open(TestDB testDB) throws Exception {
104     return open(testDB.getExpectedFileFormat(), testDB.getFile(), false,
105                 testDB.getExpectedCharset());
106   }
107 
108   public static Database openMem(TestDB testDB) throws Exception {
109     return openDB(testDB.getExpectedFileFormat(), testDB.getFile(), true,
110                     testDB.getExpectedCharset(), false);
111   }
112 
113   public static Database create(FileFormat fileFormat) throws Exception {
114     return create(fileFormat, false);
115   }
116 
117   public static Database create(FileFormat fileFormat, boolean keep)
118     throws Exception
119   {
120     return create(fileFormat, keep, true);
121   }
122 
123   public static Database createMem(FileFormat fileFormat) throws Exception {
124     return create(fileFormat);
125   }
126 
127   public static Database createFile(FileFormat fileFormat) throws Exception {
128     return create(fileFormat, false, false);
129   }
130 
131   private static Database create(FileFormat fileFormat, boolean keep,
132                                  boolean inMem)
133     throws Exception
134   {
135 
136     FileChannel channel = ((inMem && !keep) ? MemFileChannel.newChannel() :
137                            null);
138 
139     if (fileFormat == FileFormat.GENERIC_JET4) {
140       // while we don't support creating GENERIC_JET4 as a jackcess feature,
141       // we do want to be able to test these types of dbs
142       InputStream inStream = null;
143       OutputStream outStream = null;
144       try {
145         inStream = TestUtil.class.getClassLoader()
146           .getResourceAsStream("emptyJet4.mdb");
147         File f = createTempFile(keep);
148         if (channel != null) {
149           JetFormatTest.transferDbFrom(channel, inStream);
150         } else {
151           ByteUtil.copy(inStream, outStream = new FileOutputStream(f));
152           outStream.close();
153         }
154         return new DatabaseBuilder(f)
155           .setAutoSync(getTestAutoSync()).setChannel(channel).open();
156       } finally {
157         ByteUtil.closeQuietly(inStream);
158         ByteUtil.closeQuietly(outStream);
159       }
160     }
161 
162     return new DatabaseBuilder(createTempFile(keep)).setFileFormat(fileFormat)
163         .setAutoSync(getTestAutoSync()).setChannel(channel).create();
164   }
165 
166 
167   public static Database openCopy(TestDB testDB) throws Exception {
168     return openCopy(testDB, false);
169   }
170 
171   public static Database openCopy(TestDB testDB, boolean keep)
172     throws Exception
173   {
174     return openCopy(testDB.getExpectedFileFormat(), testDB.getFile(), keep);
175   }
176 
177   public static Database openCopy(FileFormat fileFormat, File file)
178     throws Exception
179   {
180     return openCopy(fileFormat, file, false);
181   }
182 
183   public static Database openCopy(FileFormat fileFormat, File file,
184                                   boolean keep)
185     throws Exception
186   {
187     File tmp = createTempFile(keep);
188     copyFile(file, tmp);
189     return openDB(fileFormat, tmp, false, null, false);
190   }
191 
192   private static Database openDB(
193       FileFormat fileFormat, File file, boolean inMem, Charset charset,
194       boolean readOnly)
195     throws Exception
196   {
197     FileChannel channel = (inMem ? MemFileChannel.newChannel(
198                                file, MemFileChannel.RW_CHANNEL_MODE)
199                            : null);
200     final Database db = new DatabaseBuilder(file).setReadOnly(readOnly)
201       .setAutoSync(getTestAutoSync()).setChannel(channel)
202       .setCharset(charset).open();
203     if(fileFormat != null) {
204       Assert.assertEquals(
205           "Wrong JetFormat.",
206           DatabaseImpl.getFileFormatDetails(fileFormat).getFormat(),
207           ((DatabaseImpl)db).getFormat());
208       Assert.assertEquals(
209           "Wrong FileFormat.", fileFormat, db.getFileFormat());
210     }
211     return db;
212   }
213 
214   static Object[] createTestRow(String col1Val) {
215     return new Object[] {col1Val, "R", "McCune", 1234, (byte) 0xad, 555.66d,
216         777.88f, (short) 999, new Date()};
217   }
218 
219   public static Object[] createTestRow() {
220     return createTestRow("Tim");
221   }
222 
223   static Map<String,Object> createTestRowMap(String col1Val) {
224     return createExpectedRow("A", col1Val, "B", "R", "C", "McCune",
225                              "D", 1234, "E", (byte) 0xad, "F", 555.66d,
226                              "G", 777.88f, "H", (short) 999, "I", new Date());
227   }
228 
229   public static void createTestTable(Database db) throws Exception {
230     new TableBuilder("test")
231       .addColumn(new ColumnBuilder("A", DataType.TEXT))
232       .addColumn(new ColumnBuilder("B", DataType.TEXT))
233       .addColumn(new ColumnBuilder("C", DataType.TEXT))
234       .addColumn(new ColumnBuilder("D", DataType.LONG))
235       .addColumn(new ColumnBuilder("E", DataType.BYTE))
236       .addColumn(new ColumnBuilder("F", DataType.DOUBLE))
237       .addColumn(new ColumnBuilder("G", DataType.FLOAT))
238       .addColumn(new ColumnBuilder("H", DataType.INT))
239       .addColumn(new ColumnBuilder("I", DataType.SHORT_DATE_TIME))
240       .toTable(db);
241   }
242 
243   public static String createString(int len) {
244     return createString(len, 'a');
245   }
246 
247   static String createNonAsciiString(int len) {
248     return createString(len, '\u0CC0');
249   }
250 
251   private static String createString(int len, char firstChar) {
252     StringBuilder builder = new StringBuilder(len);
253     for(int i = 0; i < len; ++i) {
254       builder.append((char)(firstChar + (i % 26)));
255     }
256     return builder.toString();
257   }
258 
259   static void assertRowCount(int expectedRowCount, Table table)
260     throws Exception
261   {
262     Assert.assertEquals(expectedRowCount, countRows(table));
263     Assert.assertEquals(expectedRowCount, table.getRowCount());
264   }
265 
266   public static int countRows(Table table) throws Exception {
267     int rtn = 0;
268     for(Map<String, Object> row : CursorBuilder.createCursor(table)) {
269       rtn++;
270     }
271     return rtn;
272   }
273 
274   public static void assertTable(
275       List<? extends Map<String, Object>> expectedTable,
276       Table table)
277     throws IOException
278   {
279     assertCursor(expectedTable, CursorBuilder.createCursor(table));
280   }
281 
282   public static void assertCursor(
283       List<? extends Map<String, Object>> expectedTable,
284       Cursor cursor)
285   {
286     List<Map<String, Object>> foundTable =
287       new ArrayList<Map<String, Object>>();
288     for(Map<String, Object> row : cursor) {
289       foundTable.add(row);
290     }
291     Assert.assertEquals(expectedTable.size(), foundTable.size());
292     for(int i = 0; i < expectedTable.size(); ++i) {
293       Assert.assertEquals(expectedTable.get(i), foundTable.get(i));
294     }
295   }
296 
297   public static RowImpl createExpectedRow(Object... rowElements) {
298     RowImpl row = new RowImpl((RowIdImpl)null);
299     for(int i = 0; i < rowElements.length; i += 2) {
300       row.put((String)rowElements[i],
301               rowElements[i + 1]);
302     }
303     return row;
304   }
305 
306   public static List<Row> createExpectedTable(Row... rows) {
307     return Arrays.<Row>asList(rows);
308   }
309 
310   public static void dumpDatabase(Database mdb) throws Exception {
311     dumpDatabase(mdb, false);
312   }
313 
314   public static void dumpDatabase(Database mdb, boolean systemTables)
315     throws Exception
316   {
317     dumpDatabase(mdb, systemTables, new PrintWriter(System.out, true));
318   }
319 
320   public static void dumpTable(Table table) throws Exception {
321     dumpTable(table, new PrintWriter(System.out, true));
322   }
323 
324   public static void dumpProperties(Table table) throws Exception {
325     System.out.println("TABLE_PROPS: " + table.getName() + ": " +
326                        table.getProperties());
327     for(Column c : table.getColumns()) {
328       System.out.println("COL_PROPS: " + c.getName() + ": " +
329                          c.getProperties());
330     }
331   }
332 
333   static void dumpDatabase(Database mdb, boolean systemTables,
334                            PrintWriter writer) throws Exception
335   {
336     writer.println("DATABASE:");
337     for(Table table : mdb) {
338       dumpTable(table, writer);
339     }
340     if(systemTables) {
341       for(String sysTableName : mdb.getSystemTableNames()) {
342         dumpTable(mdb.getSystemTable(sysTableName), writer);
343       }
344     }
345   }
346 
347   static void dumpTable(Table table, PrintWriter writer) throws Exception {
348     // make sure all indexes are read
349     for(Index index : table.getIndexes()) {
350       ((IndexImpl)index).initialize();
351     }
352 
353     writer.println("TABLE: " + table.getName());
354     List<String> colNames = new ArrayList<String>();
355     for(Column col : table.getColumns()) {
356       colNames.add(col.getName());
357     }
358     writer.println("COLUMNS: " + colNames);
359     for(Map<String, Object> row : CursorBuilder.createCursor(table)) {
360       writer.println(massageRow(row));
361     }
362   }
363 
364   private static Map<String,Object> massageRow(Map<String, Object> row)
365     throws IOException
366   {
367       for(Map.Entry<String, Object> entry : row.entrySet()) {
368         Object v = entry.getValue();
369         if(v instanceof byte[]) {
370           // make byte[] printable
371           byte[] bv = (byte[])v;
372           entry.setValue(ByteUtil.toHexString(ByteBuffer.wrap(bv), bv.length));
373         } else if(v instanceof ComplexValueForeignKey) {
374           // deref complex values
375           String str = "ComplexValue(" + v + ")" +
376             ((ComplexValueForeignKey)v).getValues();
377           entry.setValue(str);
378         }
379       }
380 
381       return row;
382   }
383 
384   static void dumpIndex(Index index) throws Exception {
385     dumpIndex(index, new PrintWriter(System.out, true));
386   }
387 
388   static void dumpIndex(Index index, PrintWriter writer) throws Exception {
389     writer.println("INDEX: " + index);
390     IndexData.EntryCursor ec = ((IndexImpl)index).cursor();
391     IndexData.Entry lastE = ec.getLastEntry();
392     IndexData.Entry e = null;
393     while((e = ec.getNextEntry()) != lastE) {
394       writer.println(e);
395     }
396   }
397 
398   static void assertSameDate(Date expected, Date found)
399   {
400     if(expected == found) {
401       return;
402     }
403     if((expected == null) || (found == null)) {
404       throw new AssertionError("Expected " + expected + ", found " + found);
405     }
406     long expTime = expected.getTime();
407     long foundTime = found.getTime();
408     // there are some rounding issues due to dates being stored as doubles,
409     // but it results in a 1 millisecond difference, so i'm not going to worry
410     // about it
411     if((expTime != foundTime) && (Math.abs(expTime - foundTime) > 1)) {
412       throw new AssertionError("Expected " + expTime + " (" + expected +
413                                "), found " + foundTime + " (" + found + ")");
414     }
415   }
416 
417   static void assertSameDate(Date expected, LocalDateTime found)
418   {
419     if((expected == null) && (found == null)) {
420       return;
421     }
422     if((expected == null) || (found == null)) {
423       throw new AssertionError("Expected " + expected + ", found " + found);
424     }
425 
426     LocalDateTime expectedLdt = LocalDateTime.ofInstant(
427         Instant.ofEpochMilli(expected.getTime()),
428         ZoneId.systemDefault());
429 
430     Assert.assertEquals(expectedLdt, found);
431   }
432 
433   public static void copyFile(File srcFile, File dstFile)
434     throws IOException
435   {
436     // FIXME should really be using commons io FileUtils here, but don't want
437     // to add dep for one simple test method
438     OutputStream ostream = new FileOutputStream(dstFile);
439     InputStream istream = new FileInputStream(srcFile);
440     try {
441       copyStream(istream, ostream);
442     } finally {
443       ostream.close();
444     }
445   }
446 
447   static void copyStream(InputStream istream, OutputStream ostream)
448     throws IOException
449   {
450     // FIXME should really be using commons io FileUtils here, but don't want
451     // to add dep for one simple test method
452     byte[] buf = new byte[1024];
453     int numBytes = 0;
454     while((numBytes = istream.read(buf)) >= 0) {
455       ostream.write(buf, 0, numBytes);
456     }
457   }
458 
459   public static File createTempFile(boolean keep) throws Exception {
460     File tmp = File.createTempFile("databaseTest", ".mdb");
461     if(keep) {
462       System.out.println("Created " + tmp);
463     } else {
464       tmp.deleteOnExit();
465     }
466     return tmp;
467   }
468 
469   public static void clearTableCache(Database db) throws Exception
470   {
471     Field f = db.getClass().getDeclaredField("_tableCache");
472     f.setAccessible(true);
473     Object val = f.get(db);
474     f = val.getClass().getDeclaredField("_tables");
475     f.setAccessible(true);
476     val = f.get(val);
477     ((Map<?,?>)val).clear();
478   }
479 
480   public static byte[] toByteArray(File file)
481     throws IOException
482   {
483     return toByteArray(new FileInputStream(file), file.length());
484   }
485 
486   public static byte[] toByteArray(InputStream in, long length)
487     throws IOException
488   {
489     // FIXME should really be using commons io IOUtils here, but don't want
490     // to add dep for one simple test method
491     try {
492       DataInputStream din = new DataInputStream(in);
493       byte[] bytes = new byte[(int)length];
494       din.readFully(bytes);
495       return bytes;
496     } finally {
497       in.close();
498     }
499   }
500 
501   static void checkTestDBTable1RowABCDEFG(final TestDB testDB, final Table table, final Row row)
502           throws IOException {
503     Assert.assertEquals("testDB: " + testDB + "; table: " + table, "abcdefg", row.get("A"));
504     Assert.assertEquals("hijklmnop", row.get("B"));
505     Assert.assertEquals(new Byte((byte) 2), row.get("C"));
506     Assert.assertEquals(new Short((short) 222), row.get("D"));
507     Assert.assertEquals(new Integer(333333333), row.get("E"));
508     Assert.assertEquals(new Double(444.555d), row.get("F"));
509     final Calendar cal = Calendar.getInstance();
510     cal.setTime(row.getDate("G"));
511     Assert.assertEquals(Calendar.SEPTEMBER, cal.get(Calendar.MONTH));
512     Assert.assertEquals(21, cal.get(Calendar.DAY_OF_MONTH));
513     Assert.assertEquals(1974, cal.get(Calendar.YEAR));
514     Assert.assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
515     Assert.assertEquals(0, cal.get(Calendar.MINUTE));
516     Assert.assertEquals(0, cal.get(Calendar.SECOND));
517     Assert.assertEquals(0, cal.get(Calendar.MILLISECOND));
518     Assert.assertEquals(Boolean.TRUE, row.get("I"));
519   }
520 
521   static void checkTestDBTable1RowA(final TestDB testDB, final Table table, final Row row)
522           throws IOException {
523     Assert.assertEquals("testDB: " + testDB + "; table: " + table, "a", row.get("A"));
524     Assert.assertEquals("b", row.get("B"));
525     Assert.assertEquals(new Byte((byte) 0), row.get("C"));
526     Assert.assertEquals(new Short((short) 0), row.get("D"));
527     Assert.assertEquals(new Integer(0), row.get("E"));
528     Assert.assertEquals(new Double(0d), row.get("F"));
529     final Calendar cal = Calendar.getInstance();
530     cal.setTime(row.getDate("G"));
531     Assert.assertEquals(Calendar.DECEMBER, cal.get(Calendar.MONTH));
532     Assert.assertEquals(12, cal.get(Calendar.DAY_OF_MONTH));
533     Assert.assertEquals(1981, cal.get(Calendar.YEAR));
534     Assert.assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
535     Assert.assertEquals(0, cal.get(Calendar.MINUTE));
536     Assert.assertEquals(0, cal.get(Calendar.SECOND));
537     Assert.assertEquals(0, cal.get(Calendar.MILLISECOND));
538     Assert.assertEquals(Boolean.FALSE, row.get("I"));
539   }
540 
541 }