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.text.SimpleDateFormat;
24  import java.util.Calendar;
25  import java.util.Date;
26  import java.util.GregorianCalendar;
27  import java.util.HashMap;
28  import java.util.Map;
29  import java.util.TimeZone;
30  
31  import com.healthmarketscience.jackcess.impl.CodecProvider;
32  import com.healthmarketscience.jackcess.impl.DatabaseImpl;
33  import com.healthmarketscience.jackcess.impl.PropertyMapImpl;
34  import com.healthmarketscience.jackcess.util.MemFileChannel;
35  
36  /**
37   * Builder style class for opening/creating a {@link Database}.
38   * <p/>
39   * Simple example usage:
40   * <pre>
41   *   Database db = DatabaseBuilder.open(new File("test.mdb"));
42   * </pre>
43   * <p/>
44   * Advanced example usage:
45   * <pre>
46   *   Database db = new DatabaseBuilder(new File("test.mdb"))
47   *     .setReadOnly(true)
48   *     .open();
49   * </pre>
50   *
51   * @author James Ahlborn
52   * @usage _general_class_
53   */
54  public class DatabaseBuilder 
55  {
56    /** the file name of the mdb to open/create */
57    private File _mdbFile;
58    /** whether or not to open existing mdb read-only */
59    private boolean _readOnly;
60    /** whether or not to auto-sync writes to the filesystem */
61    private boolean _autoSync = Database.DEFAULT_AUTO_SYNC;
62    /** optional charset for mdbs with unspecified charsets */
63    private Charset _charset;
64    /** optional timezone override for interpreting dates */
65    private TimeZone _timeZone;
66    /** optional CodecProvider for handling encoded mdbs */
67    private CodecProvider _codecProvider;
68    /** FileFormat to use when creating a new mdb */
69    private Database.FileFormat _fileFormat;
70    /** optional pre-opened FileChannel, will _not_ be closed by Database
71        close */
72    private FileChannel _channel;
73    /** database properties (if any) */
74    private Map<String,PropertyMap.Property> _dbProps;
75    /** database summary properties (if any) */
76    private Map<String,PropertyMap.Property> _summaryProps;
77    /** database user-defined (if any) */
78    private Map<String,PropertyMap.Property> _userProps;
79  
80    
81    public DatabaseBuilder() {
82      this(null);
83    }
84  
85    public DatabaseBuilder(File mdbFile) {
86      _mdbFile = mdbFile;
87    }
88  
89    /**
90     * File containing an existing database for {@link #open} or target file for
91     * new database for {@link #create} (in which case, <b>tf this file already
92     * exists, it will be overwritten.</b>)
93     * @usage _general_method_
94     */
95    public DatabaseBuilder setFile(File mdbFile) {
96      _mdbFile = mdbFile;
97      return this;
98    }
99  
100   /**
101    * Sets flag which, iff {@code true}, will force opening file in
102    * read-only mode ({@link #open} only).
103    * @usage _general_method_
104    */
105   public DatabaseBuilder setReadOnly(boolean readOnly) {
106     _readOnly = readOnly;
107     return this;
108   }
109 
110   /**
111    * Sets whether or not to enable auto-syncing on write.  if {@code true},
112    * write operations will be immediately flushed to disk upon completion.
113    * This leaves the database in a (fairly) consistent state on each write,
114    * but can be very inefficient for many updates.  if {@code false}, flushing
115    * to disk happens at the jvm's leisure, which can be much faster, but may
116    * leave the database in an inconsistent state if failures are encountered
117    * during writing.  Writes may be flushed at any time using {@link
118    * Database#flush}.
119    * @usage _intermediate_method_
120    */
121   public DatabaseBuilder setAutoSync(boolean autoSync) {
122     _autoSync = autoSync;
123     return this;
124   }
125 
126   /**
127    * Sets the Charset to use, if {@code null}, uses default.
128    * @usage _intermediate_method_
129    */
130   public DatabaseBuilder setCharset(Charset charset) {
131     _charset = charset;
132     return this;
133   }
134 
135   /**
136    * Sets the TimeZone to use for interpreting dates, if {@code null}, uses
137    * default
138    * @usage _intermediate_method_
139    */
140   public DatabaseBuilder setTimeZone(TimeZone timeZone) {
141     _timeZone = timeZone;
142     return this;
143   }
144 
145   /**
146    * Sets the CodecProvider for handling page encoding/decoding, may be
147    * {@code null} if no special encoding is necessary
148    * @usage _intermediate_method_
149    */
150   public DatabaseBuilder setCodecProvider(CodecProvider codecProvider) {
151     _codecProvider = codecProvider;
152     return this;
153   }
154 
155   /**
156    * Sets the version of new database ({@link #create} only).
157    * @usage _general_method_
158    */
159   public DatabaseBuilder setFileFormat(Database.FileFormat fileFormat) {
160     _fileFormat = fileFormat;
161     return this;
162   }
163 
164   /**
165    * Sets a pre-opened FileChannel.  if provided explicitly, <i>it will not be
166    * closed by the Database instance</i>.  This allows ultimate control of
167    * where the mdb file exists (which may not be on disk, e.g.
168    * {@link MemFileChannel}).  If provided, the File parameter will be
169    * available from {@link Database#getFile}, but otherwise ignored.
170    * @usage _advanced_method_
171    */
172   public DatabaseBuilder setChannel(FileChannel channel) {
173     _channel = channel;
174     return this;
175   }
176 
177   /**
178    * Sets the database property with the given name to the given value.
179    * Attempts to determine the type of the property (see
180    * {@link PropertyMap#put(String,Object)} for details on determining the
181    * property type).
182    */
183   public DatabaseBuilder putDatabaseProperty(String name, Object value) {
184     return putDatabaseProperty(name, null, value);
185   }
186   
187   /**
188    * Sets the database property with the given name and type to the given
189    * value.
190    */
191   public DatabaseBuilder putDatabaseProperty(String name, DataType type,
192                                              Object value) {
193     _dbProps = putProperty(_dbProps, name, type, value);
194     return this;
195   }
196   
197   /**
198    * Sets the summary database property with the given name to the given
199    * value.  Attempts to determine the type of the property (see
200    * {@link PropertyMap#put(String,Object)} for details on determining the
201    * property type).
202    */
203   public DatabaseBuilder putSummaryProperty(String name, Object value) {
204     return putSummaryProperty(name, null, value);
205   }
206   
207   /**
208    * Sets the summary database property with the given name and type to
209    * the given value.
210    */
211   public DatabaseBuilder putSummaryProperty(String name, DataType type,
212                                             Object value) {
213     _summaryProps = putProperty(_summaryProps, name, type, value);
214     return this;
215   }
216 
217   /**
218    * Sets the user-defined database property with the given name to the given
219    * value.  Attempts to determine the type of the property (see
220    * {@link PropertyMap#put(String,Object)} for details on determining the
221    * property type).
222    */
223   public DatabaseBuilder putUserDefinedProperty(String name, Object value) {
224     return putUserDefinedProperty(name, null, value);
225   }
226   
227   /**
228    * Sets the user-defined database property with the given name and type to
229    * the given value.
230    */
231   public DatabaseBuilder putUserDefinedProperty(String name, DataType type,
232                                                 Object value) {
233     _userProps = putProperty(_userProps, name, type, value);
234     return this;
235   }
236 
237   private static Map<String,PropertyMap.Property> putProperty(
238       Map<String,PropertyMap.Property> props, String name, DataType type,
239       Object value)
240   {
241     if(props == null) {
242       props = new HashMap<String,PropertyMap.Property>();
243     }
244     props.put(name, PropertyMapImpl.createProperty(name, type, value));
245     return props;
246   }
247 
248   /**
249    * Opens an existingnew Database using the configured information.
250    */
251   public Database open() throws IOException {
252     return DatabaseImpl.open(_mdbFile, _readOnly, _channel, _autoSync, _charset,
253                              _timeZone, _codecProvider);
254   }
255 
256   /**
257    * Creates a new Database using the configured information.
258    */
259   public Database create() throws IOException {
260     Database db = DatabaseImpl.create(_fileFormat, _mdbFile, _channel, _autoSync, 
261                                       _charset, _timeZone);
262     if(_dbProps != null) {
263       PropertyMap props = db.getDatabaseProperties();
264       props.putAll(_dbProps.values());
265       props.save();
266     }
267     if(_summaryProps != null) {
268       PropertyMap props = db.getSummaryProperties();
269       props.putAll(_summaryProps.values());
270       props.save();
271     }
272     if(_userProps != null) {
273       PropertyMap props = db.getUserDefinedProperties();
274       props.putAll(_userProps.values());
275       props.save();
276     }
277     return db;
278   }
279 
280   /**
281    * Open an existing Database.  If the existing file is not writeable, the
282    * file will be opened read-only.  Auto-syncing is enabled for the returned
283    * Database.
284    * 
285    * @param mdbFile File containing the database
286    * 
287    * @see DatabaseBuilder for more flexible Database opening
288    * @usage _general_method_
289    */
290   public static Database open(File mdbFile) throws IOException {
291     return new DatabaseBuilder(mdbFile).open();
292   }
293   
294   /**
295    * Create a new Database for the given fileFormat
296    * 
297    * @param fileFormat version of new database.
298    * @param mdbFile Location to write the new database to.  <b>If this file
299    *    already exists, it will be overwritten.</b>
300    *
301    * @see DatabaseBuilder for more flexible Database creation
302    * @usage _general_method_
303    */
304   public static Database create(Database.FileFormat fileFormat, File mdbFile) 
305     throws IOException 
306   {
307     return new DatabaseBuilder(mdbFile).setFileFormat(fileFormat).create();
308   }
309 
310   /**
311    * Returns a SimpleDateFormat for the given format string which is
312    * configured with a compatible Calendar instance (see
313    * {@link #toCompatibleCalendar}).
314    */
315   public static SimpleDateFormat createDateFormat(String formatStr) {
316     SimpleDateFormat sdf = new SimpleDateFormat(formatStr);
317     toCompatibleCalendar(sdf.getCalendar());
318     return sdf;
319   }
320 
321   /**
322    * Ensures that the given {@link Calendar} is configured to be compatible
323    * with how Access handles dates.  Specifically, alters the gregorian change
324    * (the java default gregorian change switches to julian dates for dates pre
325    * 1582-10-15, whereas Access uses <a href="https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar">proleptic gregorian dates</a>).
326    */
327   public static Calendar toCompatibleCalendar(Calendar cal) {
328     if(cal instanceof GregorianCalendar) {
329       ((GregorianCalendar)cal).setGregorianChange(new Date(Long.MIN_VALUE));
330     }
331     return cal;
332   }
333 }