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 }