View Javadoc
1   /*
2   Copyright (c) 2012 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.File;
20  import java.io.IOException;
21  import java.nio.channels.FileChannel;
22  import java.nio.charset.Charset;
23  import java.nio.file.Path;
24  import java.text.SimpleDateFormat;
25  import java.util.Calendar;
26  import java.util.Date;
27  import java.util.GregorianCalendar;
28  import java.util.HashMap;
29  import java.util.Map;
30  import java.util.TimeZone;
31  
32  import com.healthmarketscience.jackcess.impl.CodecProvider;
33  import com.healthmarketscience.jackcess.impl.DatabaseImpl;
34  import com.healthmarketscience.jackcess.impl.PropertyMapImpl;
35  import com.healthmarketscience.jackcess.util.MemFileChannel;
36  
37  /**
38   * Builder style class for opening/creating a {@link Database}.
39   * <p>
40   * Simple example usage:
41   * <pre>
42   *   Database db = DatabaseBuilder.open(new File("test.mdb"));
43   * </pre>
44   * <p>
45   * Advanced example usage:
46   * <pre>
47   *   Database db = new DatabaseBuilder(new File("test.mdb"))
48   *     .setReadOnly(true)
49   *     .open();
50   * </pre>
51   *
52   * @author James Ahlborn
53   * @usage _general_class_
54   */
55  public class DatabaseBuilder
56  {
57    /** the file name of the mdb to open/create */
58    private Path _mdbFile;
59    /** whether or not to open existing mdb read-only */
60    private boolean _readOnly;
61    /** whether or not to auto-sync writes to the filesystem */
62    private boolean _autoSync = Database.DEFAULT_AUTO_SYNC;
63    /** optional charset for mdbs with unspecified charsets */
64    private Charset _charset;
65    /** optional timezone override for interpreting dates */
66    private TimeZone _timeZone;
67    /** optional CodecProvider for handling encoded mdbs */
68    private CodecProvider _codecProvider;
69    /** FileFormat to use when creating a new mdb */
70    private Database.FileFormat _fileFormat;
71    /** optional pre-opened FileChannel, will _not_ be closed by Database
72        close */
73    private FileChannel _channel;
74    /** database properties (if any) */
75    private Map<String,PropertyMap.Property> _dbProps;
76    /** database summary properties (if any) */
77    private Map<String,PropertyMap.Property> _summaryProps;
78    /** database user-defined (if any) */
79    private Map<String,PropertyMap.Property> _userProps;
80    /** flag indicating that the system catalog index is borked */
81    private boolean _ignoreBrokenSystemCatalogIndex;
82  
83    public DatabaseBuilder() {
84      this((Path)null);
85    }
86  
87    public DatabaseBuilder(File mdbFile) {
88      this(toPath(mdbFile));
89    }
90  
91    public DatabaseBuilder(Path mdbFile) {
92      _mdbFile = mdbFile;
93    }
94  
95    /**
96     * File containing an existing database for {@link #open} or target file for
97     * new database for {@link #create} (in which case, <b>tf this file already
98     * exists, it will be overwritten.</b>)
99     * @usage _general_method_
100    */
101   public DatabaseBuilder setFile(File mdbFile) {
102     return setPath(toPath(mdbFile));
103   }
104 
105   /**
106    * File containing an existing database for {@link #open} or target file for
107    * new database for {@link #create} (in which case, <b>tf this file already
108    * exists, it will be overwritten.</b>)
109    * @usage _general_method_
110    */
111   public DatabaseBuilder setPath(Path mdbFile) {
112     _mdbFile = mdbFile;
113     return this;
114   }
115 
116   /**
117    * Sets flag which, iff {@code true}, will force opening file in
118    * read-only mode ({@link #open} only).
119    * @usage _general_method_
120    */
121   public DatabaseBuilder setReadOnly(boolean readOnly) {
122     _readOnly = readOnly;
123     return this;
124   }
125 
126   /**
127    * Sets whether or not to enable auto-syncing on write.  if {@code true},
128    * write operations will be immediately flushed to disk upon completion.
129    * This leaves the database in a (fairly) consistent state on each write,
130    * but can be very inefficient for many updates.  if {@code false}, flushing
131    * to disk happens at the jvm's leisure, which can be much faster, but may
132    * leave the database in an inconsistent state if failures are encountered
133    * during writing.  Writes may be flushed at any time using {@link
134    * Database#flush}.
135    * @usage _intermediate_method_
136    */
137   public DatabaseBuilder setAutoSync(boolean autoSync) {
138     _autoSync = autoSync;
139     return this;
140   }
141 
142   /**
143    * Sets the Charset to use, if {@code null}, uses default.
144    * @usage _intermediate_method_
145    */
146   public DatabaseBuilder setCharset(Charset charset) {
147     _charset = charset;
148     return this;
149   }
150 
151   /**
152    * Sets the TimeZone to use for interpreting dates, if {@code null}, uses
153    * default
154    * @usage _intermediate_method_
155    */
156   public DatabaseBuilder setTimeZone(TimeZone timeZone) {
157     _timeZone = timeZone;
158     return this;
159   }
160 
161   /**
162    * Sets the CodecProvider for handling page encoding/decoding, may be
163    * {@code null} if no special encoding is necessary
164    * @usage _intermediate_method_
165    */
166   public DatabaseBuilder setCodecProvider(CodecProvider codecProvider) {
167     _codecProvider = codecProvider;
168     return this;
169   }
170 
171   /**
172    * Sets the version of new database ({@link #create} only).
173    * @usage _general_method_
174    */
175   public DatabaseBuilder setFileFormat(Database.FileFormat fileFormat) {
176     _fileFormat = fileFormat;
177     return this;
178   }
179 
180   /**
181    * Sets a pre-opened FileChannel.  if provided explicitly, <i>it will not be
182    * closed by the Database instance</i>.  This allows ultimate control of
183    * where the mdb file exists (which may not be on disk, e.g.
184    * {@link MemFileChannel}).  If provided, the File parameter will be
185    * available from {@link Database#getFile}, but otherwise ignored.
186    * @usage _advanced_method_
187    */
188   public DatabaseBuilder setChannel(FileChannel channel) {
189     _channel = channel;
190     return this;
191   }
192 
193   /**
194    * Sets the database property with the given name to the given value.
195    * Attempts to determine the type of the property (see
196    * {@link PropertyMap#put(String,Object)} for details on determining the
197    * property type).
198    */
199   public DatabaseBuilder putDatabaseProperty(String name, Object value) {
200     return putDatabaseProperty(name, null, value);
201   }
202 
203   /**
204    * Sets the database property with the given name and type to the given
205    * value.
206    */
207   public DatabaseBuilder putDatabaseProperty(String name, DataType type,
208                                              Object value) {
209     _dbProps = putProperty(_dbProps, name, type, value);
210     return this;
211   }
212 
213   /**
214    * Sets the summary database property with the given name to the given
215    * value.  Attempts to determine the type of the property (see
216    * {@link PropertyMap#put(String,Object)} for details on determining the
217    * property type).
218    */
219   public DatabaseBuilder putSummaryProperty(String name, Object value) {
220     return putSummaryProperty(name, null, value);
221   }
222 
223   /**
224    * Sets the summary database property with the given name and type to
225    * the given value.
226    */
227   public DatabaseBuilder putSummaryProperty(String name, DataType type,
228                                             Object value) {
229     _summaryProps = putProperty(_summaryProps, name, type, value);
230     return this;
231   }
232 
233   /**
234    * Sets the user-defined database property with the given name to the given
235    * value.  Attempts to determine the type of the property (see
236    * {@link PropertyMap#put(String,Object)} for details on determining the
237    * property type).
238    */
239   public DatabaseBuilder putUserDefinedProperty(String name, Object value) {
240     return putUserDefinedProperty(name, null, value);
241   }
242 
243   /**
244    * Sets the user-defined database property with the given name and type to
245    * the given value.
246    */
247   public DatabaseBuilder putUserDefinedProperty(String name, DataType type,
248                                                 Object value) {
249     _userProps = putProperty(_userProps, name, type, value);
250     return this;
251   }
252 
253   private static Map<String,PropertyMap.Property> putProperty(
254       Map<String,PropertyMap.Property> props, String name, DataType type,
255       Object value)
256   {
257     if(props == null) {
258       props = new HashMap<String,PropertyMap.Property>();
259     }
260     props.put(name, PropertyMapImpl.createProperty(name, type, value));
261     return props;
262   }
263 
264   /**
265    * Sets flag which, if {@code true}, will make the database ignore the index
266    * on the system catalog when looking up tables.  This will make table
267    * retrieval slower, but can be used to workaround broken indexes.
268    */
269   public DatabaseBuilder setIgnoreBrokenSystemCatalogIndex(boolean ignore) {
270     _ignoreBrokenSystemCatalogIndex = ignore;
271     return this;
272   }
273 
274   /**
275    * Opens an existingnew Database using the configured information.
276    */
277   public Database open() throws IOException {
278     return DatabaseImpl.open(_mdbFile, _readOnly, _channel, _autoSync, _charset,
279                              _timeZone, _codecProvider,
280                              _ignoreBrokenSystemCatalogIndex);
281   }
282 
283   /**
284    * Creates a new Database using the configured information.
285    */
286   public Database create() throws IOException {
287     Database db = DatabaseImpl.create(_fileFormat, _mdbFile, _channel, _autoSync,
288                                       _charset, _timeZone);
289     if(_dbProps != null) {
290       PropertyMap props = db.getDatabaseProperties();
291       props.putAll(_dbProps.values());
292       props.save();
293     }
294     if(_summaryProps != null) {
295       PropertyMap props = db.getSummaryProperties();
296       props.putAll(_summaryProps.values());
297       props.save();
298     }
299     if(_userProps != null) {
300       PropertyMap props = db.getUserDefinedProperties();
301       props.putAll(_userProps.values());
302       props.save();
303     }
304     return db;
305   }
306 
307   /**
308    * Open an existing Database.  If the existing file is not writeable, the
309    * file will be opened read-only.  Auto-syncing is enabled for the returned
310    * Database.
311    *
312    * @param mdbFile File containing the database
313    *
314    * @see DatabaseBuilder for more flexible Database opening
315    * @usage _general_method_
316    */
317   public static Database open(File mdbFile) throws IOException {
318     return new DatabaseBuilder(mdbFile).open();
319   }
320 
321   /**
322    * Open an existing Database.  If the existing file is not writeable, the
323    * file will be opened read-only.  Auto-syncing is enabled for the returned
324    * Database.
325    *
326    * @param mdbFile File containing the database
327    *
328    * @see DatabaseBuilder for more flexible Database opening
329    * @usage _general_method_
330    */
331   public static Database open(Path mdbFile) throws IOException {
332     return new DatabaseBuilder(mdbFile).open();
333   }
334 
335   /**
336    * Create a new Database for the given fileFormat
337    *
338    * @param fileFormat version of new database.
339    * @param mdbFile Location to write the new database to.  <b>If this file
340    *    already exists, it will be overwritten.</b>
341    *
342    * @see DatabaseBuilder for more flexible Database creation
343    * @usage _general_method_
344    */
345   public static Database create(Database.FileFormat fileFormat, File mdbFile)
346     throws IOException
347   {
348     return new DatabaseBuilder(mdbFile).setFileFormat(fileFormat).create();
349   }
350 
351   /**
352    * Returns a SimpleDateFormat for the given format string which is
353    * configured with a compatible Calendar instance (see
354    * {@link #toCompatibleCalendar}).
355    */
356   public static SimpleDateFormat createDateFormat(String formatStr) {
357     SimpleDateFormat sdf = new SimpleDateFormat(formatStr);
358     toCompatibleCalendar(sdf.getCalendar());
359     return sdf;
360   }
361 
362   /**
363    * Ensures that the given {@link Calendar} is configured to be compatible
364    * with how Access handles dates.  Specifically, alters the gregorian change
365    * (the java default gregorian change switches to julian dates for dates pre
366    * 1582-10-15, whereas Access uses <a href="https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar">proleptic gregorian dates</a>).
367    */
368   public static Calendar toCompatibleCalendar(Calendar cal) {
369     if(cal instanceof GregorianCalendar) {
370       ((GregorianCalendar)cal).setGregorianChange(new Date(Long.MIN_VALUE));
371     }
372     return cal;
373   }
374 
375   /**
376    * Convenience method for constructing a DatabaseBuilder.
377    */
378   public static DatabaseBuilder newDatabase() {
379     return new DatabaseBuilder();
380   }
381 
382   /**
383    * Convenience method for constructing a DatabaseBuilder.
384    */
385   public static DatabaseBuilder newDatabase(Path path) {
386     return new DatabaseBuilder(path);
387   }
388 
389   /**
390    * Convenience method for constructing a DatabaseBuilder.
391    */
392   public static DatabaseBuilder newDatabase(File file) {
393     return new DatabaseBuilder(file);
394   }
395 
396   /**
397    * Convenience method for constructing a TableBuilder.
398    */
399   public static TableBuilder newTable(String name) {
400     return new TableBuilder(name);
401   }
402 
403   /**
404    * Convenience method for constructing a TableBuilder.
405    */
406   public static TableBuilder newTable(String name, boolean escapeIdentifiers) {
407     return new TableBuilder(name, escapeIdentifiers);
408   }
409 
410   /**
411    * Convenience method for constructing a ColumnBuilder.
412    */
413   public static ColumnBuilder newColumn(String name) {
414     return new ColumnBuilder(name);
415   }
416 
417   /**
418    * Convenience method for constructing a TableBuilder.
419    */
420   public static ColumnBuilder newColumn(String name, DataType type) {
421     return new ColumnBuilder(name, type);
422   }
423 
424   /**
425    * Convenience method for constructing an IndexBuilder.
426    */
427   public static IndexBuilder newIndex(String name) {
428     return new IndexBuilder(name);
429   }
430 
431   /**
432    * Convenience method for constructing a primary key IndexBuilder.
433    */
434   public static IndexBuilder newPrimaryKey(String... colNames) {
435     return new IndexBuilder(IndexBuilder.PRIMARY_KEY_NAME)
436       .addColumns(colNames)
437       .setPrimaryKey();
438   }
439 
440   /**
441    * Convenience method for constructing a RelationshipBuilder.
442    */
443   public static RelationshipBuilder newRelationship(
444       String fromTable, String toTable) {
445     return new RelationshipBuilder(fromTable, toTable);
446   }
447 
448   /**
449    * Convenience method for constructing a RelationshipBuilder.
450    */
451   public static RelationshipBuilder newRelationship(
452       Table../../../com/healthmarketscience/jackcess/Table.html#Table">Table fromTable, Table toTable) {
453     return new RelationshipBuilder(fromTable, toTable);
454   }
455 
456   private static Path toPath(File file) {
457     return ((file != null) ? file.toPath() : null);
458   }
459 }