make DBHelper follow the Java Singleton pattern

It was already behaving like a singleton, but the code was spread around in
other classes.  DBHelper does not use a private constructor though since
the tests prevent it.
This commit is contained in:
Hans-Christoph Steiner 2018-01-26 17:42:59 +01:00
parent 2fe7faed6e
commit d8879dd425
18 changed files with 44 additions and 92 deletions

View File

@ -453,8 +453,9 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
* While it is likely that most places asking for preferences have access to a {@link Context}, * While it is likely that most places asking for preferences have access to a {@link Context},
* it is a minor convenience to be able to ask for preferences without. * it is a minor convenience to be able to ask for preferences without.
*/ */
public static void clearSingletonForTesting() { public static void setupForTests(Context context) {
instance = null; instance = null;
setup(context);
} }
/** /**

View File

@ -47,13 +47,20 @@ import org.fdroid.fdroid.data.Schema.RepoTable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/**
* This is basically a singleton used to represent the database at the core
* of all of the {@link android.content.ContentProvider}s used at the core
* of this app. {@link DBHelper} is not {@code private} so that it can be easily
* used in test subclasses.
*/
@SuppressWarnings("LineLength") @SuppressWarnings("LineLength")
class DBHelper extends SQLiteOpenHelper { public class DBHelper extends SQLiteOpenHelper {
private static final String TAG = "DBHelper"; private static final String TAG = "DBHelper";
public static final int REPO_XML_ARG_COUNT = 8; public static final int REPO_XML_ARG_COUNT = 8;
private static DBHelper instance;
private static final String DATABASE_NAME = "fdroid"; private static final String DATABASE_NAME = "fdroid";
private static final String CREATE_TABLE_PACKAGE = "CREATE TABLE " + PackageTable.NAME private static final String CREATE_TABLE_PACKAGE = "CREATE TABLE " + PackageTable.NAME
@ -214,7 +221,22 @@ class DBHelper extends SQLiteOpenHelper {
DBHelper(Context context) { DBHelper(Context context) {
super(context, DATABASE_NAME, null, DB_VERSION); super(context, DATABASE_NAME, null, DB_VERSION);
this.context = context; this.context = context.getApplicationContext();
}
/**
* Only used for testing. Not quite sure how to mock a singleton variable like this.
*/
public static void clearDbHelperSingleton() {
instance = null;
}
static synchronized DBHelper getInstance(Context context) {
if (instance == null) {
Utils.debugLog(TAG, "First time accessing database, creating new helper");
instance = new DBHelper(context);
}
return instance;
} }
@Override @Override

View File

@ -5,16 +5,13 @@ import android.content.ContentProvider;
import android.content.ContentProviderOperation; import android.content.ContentProviderOperation;
import android.content.ContentProviderResult; import android.content.ContentProviderResult;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException; import android.content.OperationApplicationException;
import android.content.UriMatcher; import android.content.UriMatcher;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.Utils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
@ -23,15 +20,13 @@ import java.util.Set;
public abstract class FDroidProvider extends ContentProvider { public abstract class FDroidProvider extends ContentProvider {
private static final String TAG = "FDroidProvider"; public static final String TAG = "FDroidProvider";
static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".data"; static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".data";
static final int CODE_LIST = 1; static final int CODE_LIST = 1;
static final int CODE_SINGLE = 2; static final int CODE_SINGLE = 2;
private static DBHelper dbHelper;
private boolean isApplyingBatch; private boolean isApplyingBatch;
protected abstract String getTableName(); protected abstract String getTableName();
@ -73,28 +68,13 @@ public abstract class FDroidProvider extends ContentProvider {
return result; return result;
} }
/**
* Only used for testing. Not quite sure how to mock a singleton variable like this.
*/
public static void clearDbHelperSingleton() {
dbHelper = null;
}
private static synchronized DBHelper getOrCreateDb(Context context) {
if (dbHelper == null) {
Utils.debugLog(TAG, "First time accessing database, creating new helper");
dbHelper = new DBHelper(context);
}
return dbHelper;
}
@Override @Override
public boolean onCreate() { public boolean onCreate() {
return true; return true;
} }
protected final synchronized SQLiteDatabase db() { protected final synchronized SQLiteDatabase db() {
return getOrCreateDb(getContext().getApplicationContext()).getWritableDatabase(); return DBHelper.getInstance(getContext()).getWritableDatabase();
} }
@Override @Override

View File

@ -2,7 +2,6 @@ package org.fdroid.fdroid;
import android.app.Application; import android.app.Application;
import android.content.ContentValues; import android.content.ContentValues;
import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.App;
@ -12,7 +11,6 @@ import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.FDroidProviderTest; import org.fdroid.fdroid.data.FDroidProviderTest;
import org.fdroid.fdroid.data.InstalledAppTestUtils; import org.fdroid.fdroid.data.InstalledAppTestUtils;
import org.fdroid.fdroid.data.Schema; import org.fdroid.fdroid.data.Schema;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -21,9 +19,9 @@ import org.robolectric.annotation.Config;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24) @Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@ -35,7 +33,7 @@ public class AntiFeaturesTest extends FDroidProviderTest {
@Before @Before
public void setup() { public void setup() {
Preferences.setup(context); Preferences.setupForTests(context);
ContentValues vulnValues = new ContentValues(1); ContentValues vulnValues = new ContentValues(1);
vulnValues.put(Schema.ApkTable.Cols.AntiFeatures.ANTI_FEATURES, "KnownVuln,ContainsGreenButtons"); vulnValues.put(Schema.ApkTable.Cols.AntiFeatures.ANTI_FEATURES, "KnownVuln,ContainsGreenButtons");
@ -58,11 +56,6 @@ public class AntiFeaturesTest extends FDroidProviderTest {
AppProvider.Helper.recalculatePreferredMetadata(context); AppProvider.Helper.recalculatePreferredMetadata(context);
} }
@After
public void tearDown() {
Preferences.clearSingletonForTesting();
}
private static String generateHash(String packageName, int versionCode) { private static String generateHash(String packageName, int versionCode) {
return packageName + "-" + versionCode; return packageName + "-" + versionCode;
} }

View File

@ -1,7 +1,6 @@
package org.fdroid.fdroid.data; package org.fdroid.fdroid.data;
import android.app.Application; import android.app.Application;
import org.fdroid.fdroid.Assert; import org.fdroid.fdroid.Assert;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.TestUtils;
@ -11,10 +10,10 @@ import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@Config(constants = BuildConfig.class, application = Application.class, sdk = 24) @Config(constants = BuildConfig.class, application = Application.class, sdk = 24)
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)

View File

@ -10,7 +10,6 @@ import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols; import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -40,12 +39,7 @@ public class AppProviderTest extends FDroidProviderTest {
@Before @Before
public void setup() { public void setup() {
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class); TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
Preferences.setup(context); Preferences.setupForTests(context);
}
@After
public void tearDown() {
Preferences.clearSingletonForTesting();
} }
/** /**

View File

@ -1,7 +1,6 @@
package org.fdroid.fdroid.data; package org.fdroid.fdroid.data;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.TestUtils;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@ -24,7 +23,7 @@ public abstract class FDroidProviderTest {
@After @After
public final void tearDownBase() { public final void tearDownBase() {
CategoryProvider.Helper.clearCategoryIdCache(); CategoryProvider.Helper.clearCategoryIdCache();
FDroidProvider.clearDbHelperSingleton(); DBHelper.clearDbHelperSingleton();
} }
} }

View File

@ -8,7 +8,6 @@ import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Schema.InstalledAppTable.Cols; import org.fdroid.fdroid.data.Schema.InstalledAppTable.Cols;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -31,12 +30,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
@Before @Before
public void setup() { public void setup() {
TestUtils.registerContentProvider(InstalledAppProvider.getAuthority(), InstalledAppProvider.class); TestUtils.registerContentProvider(InstalledAppProvider.getAuthority(), InstalledAppProvider.class);
Preferences.setup(context); Preferences.setupForTests(context);
}
@After
public void tearDown() {
Preferences.clearSingletonForTesting();
} }
@Test @Test

View File

@ -2,11 +2,9 @@ package org.fdroid.fdroid.data;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.TestUtils;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -24,7 +22,7 @@ public class PreferredSignatureTest extends FDroidProviderTest {
@Before @Before
public void setup() { public void setup() {
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class); TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
Preferences.setup(context); Preferences.setupForTests(context);
// This is what the FDroidApp does when this preference is changed. Need to also do this under testing. // This is what the FDroidApp does when this preference is changed. Need to also do this under testing.
Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() { Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() {
@ -35,11 +33,6 @@ public class PreferredSignatureTest extends FDroidProviderTest {
}); });
} }
@After
public void tearDown() {
Preferences.clearSingletonForTesting();
}
private Repo createFDroidRepo() { private Repo createFDroidRepo() {
return RepoProviderTest.insertRepo(context, "https://f-droid.org/fdroid/repo", "", "", ""); return RepoProviderTest.insertRepo(context, "https://f-droid.org/fdroid/repo", "", "", "");
} }

View File

@ -39,7 +39,7 @@ public class ProviderUriTests {
@After @After
public void teardown() { public void teardown() {
FDroidProvider.clearDbHelperSingleton(); DBHelper.clearDbHelperSingleton();
} }
@Test @Test

View File

@ -26,7 +26,6 @@ import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;

View File

@ -5,7 +5,6 @@ import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols; import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -24,7 +23,7 @@ public class SuggestedVersionTest extends FDroidProviderTest {
@Before @Before
public void setup() { public void setup() {
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class); TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
Preferences.setup(context); Preferences.setupForTests(context);
// This is what the FDroidApp does when this preference is changed. Need to also do this under testing. // This is what the FDroidApp does when this preference is changed. Need to also do this under testing.
Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() { Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() {
@ -35,11 +34,6 @@ public class SuggestedVersionTest extends FDroidProviderTest {
}); });
} }
@After
public void tearDown() {
Preferences.clearSingletonForTesting();
}
@Test @Test
public void singleRepoSingleSig() { public void singleRepoSingleSig() {
App singleApp = TestUtils.insertApp( App singleApp = TestUtils.insertApp(

View File

@ -4,7 +4,6 @@ package org.fdroid.fdroid.updater;
import android.content.ContentValues; import android.content.ContentValues;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.util.Log; import android.util.Log;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.RepoUpdater.UpdateException; import org.fdroid.fdroid.RepoUpdater.UpdateException;
import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.Repo;

View File

@ -26,7 +26,6 @@ import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.RepoPushRequest; import org.fdroid.fdroid.data.RepoPushRequest;
import org.fdroid.fdroid.mock.RepoDetails; import org.fdroid.fdroid.mock.RepoDetails;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -66,12 +65,7 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
@Before @Before
public void setup() { public void setup() {
Preferences.setup(context); Preferences.setupForTests(context);
}
@After
public void tearDown() {
Preferences.clearSingletonForTesting();
} }
@Test @Test

View File

@ -1,7 +1,6 @@
package org.fdroid.fdroid.updater; package org.fdroid.fdroid.updater;
import android.content.ContentValues; import android.content.ContentValues;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.RepoUpdater; import org.fdroid.fdroid.RepoUpdater;
import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.Apk;

View File

@ -17,7 +17,6 @@ import org.fdroid.fdroid.data.FDroidProviderTest;
import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema; import org.fdroid.fdroid.data.Schema;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import java.io.File; import java.io.File;
@ -86,12 +85,7 @@ public abstract class MultiRepoUpdaterTest extends FDroidProviderTest {
RepoProvider.Helper.remove(context, 3); RepoProvider.Helper.remove(context, 3);
RepoProvider.Helper.remove(context, 4); RepoProvider.Helper.remove(context, 4);
Preferences.setup(context); Preferences.setupForTests(context);
}
@After
public final void tearDownMultiRepo() {
Preferences.clearSingletonForTesting();
} }
protected void assertApp(String packageName, int[] versionCodes) { protected void assertApp(String packageName, int[] versionCodes) {

View File

@ -4,7 +4,6 @@ package org.fdroid.fdroid.updater;
import android.content.ContentValues; import android.content.ContentValues;
import android.support.annotation.StringDef; import android.support.annotation.StringDef;
import android.util.Log; import android.util.Log;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.RepoUpdater; import org.fdroid.fdroid.RepoUpdater;
import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.TestUtils;

View File

@ -14,7 +14,7 @@ import org.fdroid.fdroid.R;
import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProviderTest; import org.fdroid.fdroid.data.AppProviderTest;
import org.fdroid.fdroid.data.FDroidProvider; import org.fdroid.fdroid.data.DBHelper;
import org.fdroid.fdroid.data.FDroidProviderTest; import org.fdroid.fdroid.data.FDroidProviderTest;
import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProviderTest; import org.fdroid.fdroid.data.RepoProviderTest;
@ -36,7 +36,7 @@ public class AppDetailsAdapterTest extends FDroidProviderTest {
@Before @Before
public void setup() { public void setup() {
ImageLoader.getInstance().init(ImageLoaderConfiguration.createDefault(context)); ImageLoader.getInstance().init(ImageLoaderConfiguration.createDefault(context));
Preferences.setup(context); Preferences.setupForTests(context);
Repo repo = RepoProviderTest.insertRepo(context, "http://www.example.com/fdroid/repo", "", "", "Test Repo"); Repo repo = RepoProviderTest.insertRepo(context, "http://www.example.com/fdroid/repo", "", "", "Test Repo");
app = AppProviderTest.insertApp(contentResolver, context, "com.example.app", "Test App", app = AppProviderTest.insertApp(contentResolver, context, "com.example.app", "Test App",
@ -46,8 +46,7 @@ public class AppDetailsAdapterTest extends FDroidProviderTest {
@After @After
public void teardown() { public void teardown() {
ImageLoader.getInstance().destroy(); ImageLoader.getInstance().destroy();
FDroidProvider.clearDbHelperSingleton(); DBHelper.clearDbHelperSingleton();
Preferences.clearSingletonForTesting();
} }
@Test @Test