11/*
2- * Copyright 2017-2023 ObjectBox Ltd. All rights reserved.
2+ * Copyright 2017-2024 ObjectBox Ltd. All rights reserved.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1616
1717package io .objectbox ;
1818
19+ import org .greenrobot .essentials .io .IoUtils ;
20+
1921import java .io .BufferedInputStream ;
2022import java .io .BufferedOutputStream ;
2123import java .io .File ;
4345import io .objectbox .exception .DbMaxReadersExceededException ;
4446import io .objectbox .flatbuffers .FlatBufferBuilder ;
4547import io .objectbox .ideasonly .ModelUpdate ;
46- import org .greenrobot .essentials .io .IoUtils ;
4748
4849/**
4950 * Configures and builds a {@link BoxStore} with reasonable defaults. To get an instance use {@code MyObjectBox.builder()}.
@@ -77,6 +78,9 @@ public class BoxStoreBuilder {
7778 /** Ignored by BoxStore */
7879 private String name ;
7980
81+ /** If non-null, using an in-memory database with this identifier. */
82+ private String inMemory ;
83+
8084 /** Defaults to {@link #DEFAULT_MAX_DB_SIZE_KBYTE}. */
8185 long maxSizeInKByte = DEFAULT_MAX_DB_SIZE_KBYTE ;
8286
@@ -92,8 +96,6 @@ public class BoxStoreBuilder {
9296
9397 int debugFlags ;
9498
95- private boolean android ;
96-
9799 boolean debugRelations ;
98100
99101 int fileMode ;
@@ -134,6 +136,8 @@ private BoxStoreBuilder() {
134136 /** Called internally from the generated class "MyObjectBox". Check MyObjectBox.builder() to get an instance. */
135137 @ Internal
136138 public BoxStoreBuilder (byte [] model ) {
139+ // Note: annotations do not guarantee parameter is non-null.
140+ //noinspection ConstantValue
137141 if (model == null ) {
138142 throw new IllegalArgumentException ("Model may not be null" );
139143 }
@@ -142,16 +146,15 @@ public BoxStoreBuilder(byte[] model) {
142146 }
143147
144148 /**
145- * Name of the database, which will be used as a directory for DB files.
149+ * Name of the database, which will be used as a directory for database files.
146150 * You can also specify a base directory for this one using {@link #baseDirectory(File)}.
147- * Cannot be used in combination with {@link #directory(File)}.
151+ * Cannot be used in combination with {@link #directory(File)} and {@link #inMemory(String)} .
148152 * <p>
149153 * Default: "objectbox", {@link #DEFAULT_NAME} (unless {@link #directory(File)} is used)
150154 */
151155 public BoxStoreBuilder name (String name ) {
152- if (directory != null ) {
153- throw new IllegalArgumentException ("Already has directory, cannot assign name" );
154- }
156+ checkIsNull (directory , "Already has directory, cannot assign name" );
157+ checkIsNull (inMemory , "Already set to in-memory database, cannot assign name" );
155158 if (name .contains ("/" ) || name .contains ("\\ " )) {
156159 throw new IllegalArgumentException ("Name may not contain (back) slashes. " +
157160 "Use baseDirectory() or directory() to configure alternative directories" );
@@ -161,65 +164,89 @@ public BoxStoreBuilder name(String name) {
161164 }
162165
163166 /**
164- * The directory where all DB files should be placed in.
165- * Cannot be used in combination with {@link #name(String)}/{@link #baseDirectory(File)}.
167+ * The directory where all database files should be placed in.
168+ * <p>
169+ * If the directory does not exist, it will be created. Make sure the process has permissions to write to this
170+ * directory.
171+ * <p>
172+ * To switch to an in-memory database, use a file path with {@link BoxStore#IN_MEMORY_PREFIX} and an identifier
173+ * instead:
174+ * <p>
175+ * <pre>{@code
176+ * BoxStore inMemoryStore = MyObjectBox.builder()
177+ * .directory(BoxStore.IN_MEMORY_PREFIX + "notes-db")
178+ * .build();
179+ * }</pre>
180+ * Alternatively, use {@link #inMemory(String)}.
181+ * <p>
182+ * Can not be used in combination with {@link #name(String)}, {@link #baseDirectory(File)}
183+ * or {@link #inMemory(String)}.
166184 */
167185 public BoxStoreBuilder directory (File directory ) {
168- if (name != null ) {
169- throw new IllegalArgumentException ("Already has name, cannot assign directory" );
170- }
171- if (!android && baseDirectory != null ) {
172- throw new IllegalArgumentException ("Already has base directory, cannot assign directory" );
173- }
186+ checkIsNull (name , "Already has name, cannot assign directory" );
187+ checkIsNull (inMemory , "Already set to in-memory database, cannot assign directory" );
188+ checkIsNull (baseDirectory , "Already has base directory, cannot assign directory" );
174189 this .directory = directory ;
175190 return this ;
176191 }
177192
178193 /**
179194 * In combination with {@link #name(String)}, this lets you specify the location of where the DB files should be
180195 * stored.
181- * Cannot be used in combination with {@link #directory(File)}.
196+ * Cannot be used in combination with {@link #directory(File)} or {@link #inMemory(String)} .
182197 */
183198 public BoxStoreBuilder baseDirectory (File baseDirectory ) {
184- if (directory != null ) {
185- throw new IllegalArgumentException ("Already has directory, cannot assign base directory" );
186- }
199+ checkIsNull (directory , "Already has directory, cannot assign base directory" );
200+ checkIsNull (inMemory , "Already set to in-memory database, cannot assign base directory" );
187201 this .baseDirectory = baseDirectory ;
188202 return this ;
189203 }
190204
191205 /**
192- * On Android, you can pass a Context to set the base directory using this method.
193- * This will conveniently configure the storage location to be in the files directory of your app.
206+ * Switches to an in-memory database using the given name as its identifier.
207+ * <p>
208+ * Can not be used in combination with {@link #name(String)}, {@link #directory(File)}
209+ * or {@link #baseDirectory(File)}.
210+ */
211+ public BoxStoreBuilder inMemory (String identifier ) {
212+ checkIsNull (name , "Already has name, cannot switch to in-memory database" );
213+ checkIsNull (directory , "Already has directory, cannot switch to in-memory database" );
214+ checkIsNull (baseDirectory , "Already has base directory, cannot switch to in-memory database" );
215+ inMemory = identifier ;
216+ return this ;
217+ }
218+
219+ /**
220+ * Use to check conflicting properties are not set.
221+ * If not null, throws {@link IllegalStateException} with the given message.
222+ */
223+ private static void checkIsNull (@ Nullable Object value , String errorMessage ) {
224+ if (value != null ) {
225+ throw new IllegalStateException (errorMessage );
226+ }
227+ }
228+
229+ /**
230+ * Use on Android to pass a <a href="https://developer.android.com/reference/android/content/Context">Context</a>
231+ * for loading the native library and, if not an {@link #inMemory(String)} database, for creating the base
232+ * directory for database files in the
233+ * <a href="https://developer.android.com/reference/android/content/Context#getFilesDir()">files directory of the app</a>.
194234 * <p>
195- * In more detail, this assigns the base directory (see {@link #baseDirectory}) to
235+ * In more detail, upon {@link #build()} assigns the base directory (see {@link #baseDirectory}) to
196236 * {@code context.getFilesDir() + "/objectbox/"}.
197- * Thus, when using the default name (also "objectbox" unless overwritten using {@link #name(String)}), the default
198- * location of DB files will be "objectbox/objectbox/" inside the app files directory.
199- * If you specify a custom name, for example with {@code name("foobar")}, it would become
200- * "objectbox/foobar/".
237+ * Thus, when using the default name (also "objectbox", unless overwritten using {@link #name(String)}), the default
238+ * location of database files will be "objectbox/objectbox/" inside the app's files directory.
239+ * If a custom name is specified, for example with {@code name("foobar")}, it would become "objectbox/foobar/".
201240 * <p>
202- * Alternatively, you can also use {@link #baseDirectory} or {@link #directory(File)} instead.
241+ * Use {@link #baseDirectory(File)} or {@link #directory(File)} to specify a different directory for the database
242+ * files.
203243 */
204244 public BoxStoreBuilder androidContext (Object context ) {
205245 //noinspection ConstantConditions Annotation does not enforce non-null.
206246 if (context == null ) {
207247 throw new NullPointerException ("Context may not be null" );
208248 }
209249 this .context = getApplicationContext (context );
210-
211- File baseDir = getAndroidBaseDir (context );
212- if (!baseDir .exists ()) {
213- baseDir .mkdir ();
214- if (!baseDir .exists ()) { // check baseDir.exists() because of potential concurrent processes
215- throw new RuntimeException ("Could not init Android base dir at " + baseDir .getAbsolutePath ());
216- }
217- }
218- if (!baseDir .isDirectory ()) {
219- throw new RuntimeException ("Android base dir is not a dir: " + baseDir .getAbsolutePath ());
220- }
221- baseDirectory = baseDir ;
222- android = true ;
223250 return this ;
224251 }
225252
@@ -504,7 +531,7 @@ public BoxStoreBuilder debugRelations() {
504531 * {@link DbException} are thrown during query execution).
505532 *
506533 * @param queryAttempts number of attempts a query find operation will be executed before failing.
507- * Recommended values are in the range of 2 to 5, e.g. a value of 3 as a starting point.
534+ * Recommended values are in the range of 2 to 5, e.g. a value of 3 as a starting point.
508535 */
509536 @ Experimental
510537 public BoxStoreBuilder queryAttempts (int queryAttempts ) {
@@ -580,14 +607,36 @@ byte[] buildFlatStoreOptions(String canonicalPath) {
580607 }
581608
582609 /**
583- * Builds a {@link BoxStore} using any given configuration.
610+ * Builds a {@link BoxStore} using the current configuration of this builder.
611+ *
612+ * <p>If {@link #androidContext(Object)} was called and no {@link #directory(File)} or {@link #baseDirectory(File)}
613+ * is configured, creates and sets {@link #baseDirectory(File)} as explained in {@link #androidContext(Object)}.
584614 */
585615 public BoxStore build () {
616+ // If in-memory, use a special directory (it will never be created)
617+ if (inMemory != null ) {
618+ directory = new File (BoxStore .IN_MEMORY_PREFIX + inMemory );
619+ }
620+ // On Android, create and set base directory if no directory is explicitly configured
621+ if (directory == null && baseDirectory == null && context != null ) {
622+ File baseDir = getAndroidBaseDir (context );
623+ if (!baseDir .exists ()) {
624+ baseDir .mkdir ();
625+ if (!baseDir .exists ()) { // check baseDir.exists() because of potential concurrent processes
626+ throw new RuntimeException ("Could not init Android base dir at " + baseDir .getAbsolutePath ());
627+ }
628+ }
629+ if (!baseDir .isDirectory ()) {
630+ throw new RuntimeException ("Android base dir is not a dir: " + baseDir .getAbsolutePath ());
631+ }
632+ baseDirectory = baseDir ;
633+ }
586634 if (directory == null ) {
587- name = dbName (name );
588635 directory = getDbDir (baseDirectory , name );
589636 }
590- checkProvisionInitialDbFile ();
637+ if (inMemory == null ) {
638+ checkProvisionInitialDbFile ();
639+ }
591640 return new BoxStore (this );
592641 }
593642
0 commit comments