Commits (185)
......@@ -4,9 +4,12 @@ buildscript {
repositories {
jcenter()
google()
maven {
url "https://maven.google.com"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.android.tools.build:gradle:3.1.0'
}
}
......@@ -14,6 +17,9 @@ allprojects {
repositories {
jcenter()
google()
maven {
url "https://maven.google.com"
}
}
}
......
#Thu Oct 26 14:24:32 CEST 2017
#Tue Mar 27 14:07:29 CEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
......@@ -36,6 +36,7 @@ dependencies {
implementation 'com.android.support:design:27.1.0'
implementation 'com.android.support:support-compat:27.1.0'
implementation 'com.android.support:preference-v14:27.1.0'
implementation 'com.google.code.gson:gson:2.8.2'
testImplementation 'junit:junit:4.12'
testImplementation "org.robolectric:robolectric:3.6.1"
implementation 'com.android.support:support-annotations:27.1.0'
......
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.frostnerd.utils">
<manifest package="com.frostnerd.utils">
</manifest>
package com.frostnerd.utils.adapters;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import java.util.HashMap;
import java.util.Map;
/**
* Copyright Daniel Wolf 2018
* All rights reserved.
* Code may NOT be used without proper permission, neither in binary nor in source form.
* All redistributions of this software in source code must retain this copyright header
* All redistributions of this software in binary form must visibly inform users about usage of this software
* <p>
* development@frostnerd.com
*/
public abstract class BaseAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
@NonNull private HashMap<VH, Integer> viewHolders = new HashMap<>();
private boolean destroyed = false;
@Override
@CallSuper
public void onBindViewHolder(@NonNull VH holder, int position) {
if(!destroyed) {
viewHolders.put(holder, position);
}
}
public VH findViewHolderForItemPosition(int position){
for(Map.Entry<VH, Integer> entry: viewHolders.entrySet()){
if(entry.getValue() == position)return entry.getKey();
}
return null;
}
@SuppressWarnings("ConstantConditions")
public final void destroy(){
if(destroyed)return;
destroyed = true;
viewHolders.clear();
viewHolders = null;
cleanup();
}
public boolean isDestroyed() {
return destroyed;
}
protected abstract void cleanup();
@Override
protected void finalize() throws Throwable {
super.finalize();
destroy();
}
}
package com.frostnerd.utils.adapters;
import android.content.Context;
import android.database.Cursor;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.LayoutRes;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.frostnerd.utils.database.DatabaseHelper;
import com.frostnerd.utils.database.orm.Entity;
import com.frostnerd.utils.database.orm.parser.columns.Column;
import com.frostnerd.utils.database.orm.parser.columns.SimpleColumn;
import com.frostnerd.utils.database.orm.parser.ParsedEntity;
import com.frostnerd.utils.database.orm.parser.columns.SimpleColumn;
import com.frostnerd.utils.database.orm.statementoptions.queryoptions.LimitOption;
import com.frostnerd.utils.database.orm.statementoptions.queryoptions.OrderOption;
import com.frostnerd.utils.database.orm.statementoptions.queryoptions.QueryOption;
import com.frostnerd.utils.database.orm.statementoptions.queryoptions.WhereCondition;
import com.frostnerd.utils.general.ArrayUtil;
import com.frostnerd.utils.general.PeekIterator;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;
/**
* Copyright Daniel Wolf 2017
......@@ -37,10 +35,11 @@ import java.util.List;
* <p>
* development@frostnerd.com
*/
public abstract class DatabaseAdapter<T extends Entity, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH>{
public abstract class DatabaseAdapter<T extends Entity, VH extends RecyclerView.ViewHolder> extends BaseAdapter<VH>{
private final SparseIntArray rowRemap = new SparseIntArray();
private final List<Integer> rows = new ArrayList<>();
private final HashMap<Filter, String> filterValues = new HashMap<>();
private final TreeMap<Integer, Integer> unmappablePositions = new TreeMap<>(); //Key = Positions where a different ViewType than the default one is used. Value = Corresponding offset which has to be applied to the position
private boolean update = true, resourceIntensive = false, rowsCached;
private QueryOption[] queryOptions;
private WhereCondition[] whereConditions;
......@@ -69,6 +68,7 @@ public abstract class DatabaseAdapter<T extends Entity, VH extends RecyclerView.
this(databaseHelper, MAX_ROW_ID_CACHE_COUNT, null);
}
@CallSuper
public void insertAtFront(int itemRowID, boolean notifyUpdate){
if(whereConditions != null && whereConditions.length != 0
&& !rowWithIDMatchesConditions(itemRowID))return;
......@@ -99,14 +99,17 @@ public abstract class DatabaseAdapter<T extends Entity, VH extends RecyclerView.
ArrayUtil.combine(WhereCondition.class, WhereCondition.equal(rowIDColumn, "" + rowID), whereConditions)) != 0;
}
@CallSuper
public void setOrderOption(OrderOption orderOption) {
this.orderOption = orderOption;
}
@CallSuper
public void setReloadCallback(Runnable reloadCallback) {
this.reloadCallback = reloadCallback;
}
@CallSuper
public void setOnRowLoaded(OnRowLoaded<T, VH> onRowLoaded) {
this.onRowLoaded = onRowLoaded;
}
......@@ -116,6 +119,15 @@ public abstract class DatabaseAdapter<T extends Entity, VH extends RecyclerView.
.getGenericSuperclass()).getActualTypeArguments()[0];
}
@Override
public int getItemViewType(int position) {
return getDefaultViewType();
}
public int getDefaultViewType(){
return 0;
}
public final void setProgressView(View progressView) {
this.progressView = progressView;
}
......@@ -132,8 +144,8 @@ public abstract class DatabaseAdapter<T extends Entity, VH extends RecyclerView.
return filterValues.get(filter);
}
public void reloadData(){
if(!update)return;
public final void reloadData(){
if(!update || !beforeReload())return;
resourceIntensive = false;
rowsCached = false;
whereConditions = getFilterConditions();
......@@ -156,6 +168,12 @@ public abstract class DatabaseAdapter<T extends Entity, VH extends RecyclerView.
}.start();
}
/**
* Called before the reload is dispatched
* @return Whether the adapter should reload
*/
protected abstract boolean beforeReload();
private void evaluateData(){
Cursor cursor;
rows.clear();
......@@ -180,6 +198,14 @@ public abstract class DatabaseAdapter<T extends Entity, VH extends RecyclerView.
count = queryDBCount();
loadRowRemap(0);
}
unmappablePositions.clear();
int defaultViewType = getDefaultViewType(), count = 0;
for(int i = 0; i < getItemCount(); i++){
if(getItemViewType(i) != defaultViewType){
unmappablePositions.put(i, ++count);
}
}
}
private int queryDBCount(){
......@@ -260,7 +286,36 @@ public abstract class DatabaseAdapter<T extends Entity, VH extends RecyclerView.
}
@Override
public void onBindViewHolder(VH holder, int position) {
public final void onBindViewHolder(@NonNull VH holder, int position) {
super.onBindViewHolder(holder, position);
if (unmappablePositions.size() != 0) {
if (unmappablePositions.size() == 1) {
if (unmappablePositions.firstKey() == position) {
onRowLoaded.bindNonEntityRow(holder, position);
return;
} else {
position -= 1;
}
} else {
PeekIterator<Integer> peekIterator = new PeekIterator<>(unmappablePositions.keySet().iterator());
int current;
while (peekIterator.hasNext()) {
current = peekIterator.next();
if (position == current) {
onRowLoaded.bindNonEntityRow(holder, position);
return;
} else if (position > current) {
if (peekIterator.hasNext() && position >= peekIterator.peek()) continue;
position -= unmappablePositions.get(current);
break;
}
}
}
}
onRowLoaded.bindRow(holder, getEntityAtPosition(position), position);
}
public final T getEntityAtPosition(int position){
T entity;
if(resourceIntensive){
if(!rowsCached){
......@@ -286,18 +341,20 @@ public abstract class DatabaseAdapter<T extends Entity, VH extends RecyclerView.
if(entity == null)
throw new IllegalStateException("Row with ROWID " + rowID + " is null.");
}
onRowLoaded.bindRow(holder, entity);
return entity;
}
@Override
public final int getItemCount() {
return count;
return count + unmappablePositions.size();
}
public final void cleanup(){
@CallSuper
protected void cleanup(){
rowRemap.clear();
rows.clear();
filterValues.clear();
update = false;
queryOptions = null;
whereConditions = null;
progressView = null;
......@@ -321,6 +378,7 @@ public abstract class DatabaseAdapter<T extends Entity, VH extends RecyclerView.
}
public interface OnRowLoaded<T extends Entity, VH extends RecyclerView.ViewHolder>{
void bindRow(VH view, T entity);
void bindRow(VH view, T entity, int position);
void bindNonEntityRow(VH view, int position);
}
}
......@@ -148,15 +148,15 @@ public class DataExchangeHandle {
}
public interface OnMessageReceivedListener {
public boolean handleMessage(Message message);
boolean handleMessage(Message message);
}
public interface OnConnectionReadyListener {
public void connectionReady(DataExchangeHandle handle);
void connectionReady(DataExchangeHandle handle);
}
public interface OnExchangerAnswerListener<A> {
public void answerReceived(A answer);
void answerReceived(A answer);
}
private static class IncomingHandler extends Handler {
......
package com.frostnerd.utils.apis;
import android.content.Context;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
......@@ -8,6 +7,8 @@ import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.frostnerd.utils.preferences.Preferences;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
......@@ -26,7 +27,6 @@ import java.util.Set;
* development@frostnerd.com
*/
public abstract class DataExchanger<R extends Serializable, A extends DataExchanger.DataExchangerAnswer> implements Serializable {
private int requestID;
private A answerData;
private R requestData;
private String bundleKey = "ex_" + getClass().getSimpleName() + "_" + hashCode();
......@@ -50,7 +50,7 @@ public abstract class DataExchanger<R extends Serializable, A extends DataExchan
}
@NonNull
public abstract A createAnswer(Context context);
public abstract A createAnswer(Preferences preferences);
protected void setAnswerData(A answerData) {
this.answerData = answerData;
......@@ -101,26 +101,26 @@ public abstract class DataExchanger<R extends Serializable, A extends DataExchan
@SafeVarargs
@Nullable
public static HashMap<DataExchanger, DataExchangerAnswer> executeExchangers(Context context, @NonNull Message message, Class<? extends DataExchanger>... allowedExchangers) {
return executeExchangers(context, message, new HashSet<>(Arrays.asList(allowedExchangers)));
public static HashMap<DataExchanger, DataExchangerAnswer> executeExchangers(Preferences preferences, @NonNull Message message, Class<? extends DataExchanger>... allowedExchangers) {
return executeExchangers(preferences, message, new HashSet<>(Arrays.asList(allowedExchangers)));
}
public static HashMap<DataExchanger, DataExchangerAnswer> executeExchangers(Context context, @NonNull Message message, @NonNull Set<Class<? extends DataExchanger>> allowedExchangers) {
public static HashMap<DataExchanger, DataExchangerAnswer> executeExchangers(Preferences preferences, @NonNull Message message, @NonNull Set<Class<? extends DataExchanger>> allowedExchangers) {
if (message.obj != null && message.obj instanceof Bundle)
return executeExchangers(context, (Bundle) message.obj, allowedExchangers);
return executeExchangers(preferences, (Bundle) message.obj, allowedExchangers);
return null;
}
@NonNull
public static HashMap<DataExchanger, DataExchangerAnswer> executeExchangers(Context context, @NonNull List<DataExchanger> exchangers) {
public static HashMap<DataExchanger, DataExchangerAnswer> executeExchangers(Preferences preferences, @NonNull List<DataExchanger> exchangers) {
HashMap<DataExchanger, DataExchangerAnswer> map = new HashMap<>();
for (DataExchanger exchanger : exchangers)
map.put(exchanger, exchanger.createAnswer(context));
map.put(exchanger, exchanger.createAnswer(preferences));
return map;
}
@NonNull
public static HashMap<DataExchanger, DataExchangerAnswer> executeExchangers(Context context, @Nullable Bundle exchangers, @NonNull Set<Class<? extends DataExchanger>> allowedExchangers) {
public static HashMap<DataExchanger, DataExchangerAnswer> executeExchangers(Preferences preferences, @Nullable Bundle exchangers, @NonNull Set<Class<? extends DataExchanger>> allowedExchangers) {
HashMap<DataExchanger, DataExchangerAnswer> map = new HashMap<>();
if (exchangers != null) {
Object o;
......@@ -128,7 +128,7 @@ public abstract class DataExchanger<R extends Serializable, A extends DataExchan
DataExchanger exchanger;
if ((o = exchangers.get(s)) instanceof DataExchanger && (allowedExchangers.size() == 0 || allowedExchangers.contains(o.getClass()))) {
exchanger = (DataExchanger) o;
map.put(exchanger, exchanger.createAnswer(context));
map.put(exchanger, exchanger.createAnswer(preferences));
}
}
}
......@@ -137,19 +137,19 @@ public abstract class DataExchanger<R extends Serializable, A extends DataExchan
@SafeVarargs
@NonNull
public static Bundle createAnswerBundle(Context context, @NonNull Message message, Class<? extends DataExchanger>... allowedExchangers) {
return createAnswerBundle(context, (Bundle)message.obj, new HashSet<>(Arrays.asList(allowedExchangers)));
public static Bundle createAnswerBundle(Preferences preferences, @NonNull Message message, Class<? extends DataExchanger>... allowedExchangers) {
return createAnswerBundle(preferences, (Bundle)message.obj, new HashSet<>(Arrays.asList(allowedExchangers)));
}
@SafeVarargs
@NonNull
public static Bundle createAnswerBundle(Context context, Bundle exchangers, Class<? extends DataExchanger>... allowedExchangers) {
return createAnswerBundle(context, exchangers, new HashSet<>(Arrays.asList(allowedExchangers)));
public static Bundle createAnswerBundle(Preferences preferences, Bundle exchangers, Class<? extends DataExchanger>... allowedExchangers) {
return createAnswerBundle(preferences, exchangers, new HashSet<>(Arrays.asList(allowedExchangers)));
}
@NonNull
public static Bundle createAnswerBundle(Context context, Bundle exchangers, @NonNull Set<Class<? extends DataExchanger>> allowedExchangers) {
HashMap<DataExchanger, DataExchangerAnswer> data = executeExchangers(context, exchangers, allowedExchangers);
public static Bundle createAnswerBundle(Preferences preferences, Bundle exchangers, @NonNull Set<Class<? extends DataExchanger>> allowedExchangers) {
HashMap<DataExchanger, DataExchangerAnswer> data = executeExchangers(preferences, exchangers, allowedExchangers);
Bundle args = new Bundle();
for (DataExchanger exchanger : data.keySet()) {
args.putSerializable(exchanger.bundleKey, data.get(exchanger));
......@@ -157,16 +157,16 @@ public abstract class DataExchanger<R extends Serializable, A extends DataExchan
return args;
}
public static void executeExchangersAndSendAnswers(Context context, @NonNull Message message, @NonNull Messenger receiver) throws RemoteException {
public static void executeExchangersAndSendAnswers(Preferences preferences, @NonNull Message message, @NonNull Messenger receiver) throws RemoteException {
Message m = Message.obtain();
m.obj = createAnswerBundle(context, message);
m.obj = createAnswerBundle(preferences, message);
receiver.send(m);
}
@SafeVarargs
public static void executeExchangersAndSendAnswers(Context context, @NonNull Message message, @NonNull Messenger receiver, Class<? extends DataExchanger>... allowedExchangers) throws RemoteException {
public static void executeExchangersAndSendAnswers(Preferences preferences, @NonNull Message message, @NonNull Messenger receiver, Class<? extends DataExchanger>... allowedExchangers) throws RemoteException {
Message m = Message.obtain();
m.obj = createAnswerBundle(context, message, allowedExchangers);
m.obj = createAnswerBundle(preferences, message, allowedExchangers);
receiver.send(m);
}
......
package com.frostnerd.utils.apis.dataexchangers;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import com.frostnerd.utils.apis.DataExchanger;
import com.frostnerd.utils.apis.dataexchangers.answers.PreferenceAnswer;
import com.frostnerd.utils.preferences.Preferences;
import com.frostnerd.utils.preferences.util.PreferenceHelper;
import java.io.Serializable;
import java.util.ArrayList;
......@@ -43,21 +41,20 @@ public class PreferencesExchanger extends DataExchanger<PreferencesExchanger.Pre
@NonNull
@Override
public PreferenceAnswer createAnswer(Context context) {
public PreferenceAnswer createAnswer(Preferences preferences) {
List<PreferenceAnswer.Preference> answerData = new ArrayList<>();
if(getRequestData().keys == null){
Map<String, Object> prefs = Preferences.getAll(context, false);
Map<String, Object> prefs = preferences.getAll( false);
Object value;
for(String s: prefs.keySet()){
value = prefs.get(s);
if(value instanceof Serializable)answerData.add(new PreferenceAnswer.Preference(s, Preferences.getType(value), (Serializable)value));
if(value instanceof Serializable)answerData.add(new PreferenceAnswer.Preference(s, Preferences.guessType(value), (Serializable)value));
}
}else{
Object value;
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
for(String s: getRequestData().keys){
value = Preferences.get(preferences, s, null);
if(value instanceof Serializable)answerData.add(new PreferenceAnswer.Preference(s, Preferences.getType(value), (Serializable)value));
value = preferences.get(s, null);
if(value instanceof Serializable)answerData.add(new PreferenceAnswer.Preference(s, Preferences.guessType(value), (Serializable)value));
}
}
return new PreferenceAnswer(answerData.toArray(new PreferenceAnswer.Preference[answerData.size()]));
......
package com.frostnerd.utils.apis.dataexchangers.answers;
import com.frostnerd.utils.apis.DataExchanger;
import com.frostnerd.utils.preferences.Preferences;
import com.frostnerd.utils.preferences.restrictions.Type;
import java.io.Serializable;
......@@ -18,10 +18,10 @@ public class PreferenceAnswer extends DataExchanger.DataExchangerAnswer<Preferen
public static class Preference implements Serializable {
private String key;
private Preferences.PreferenceType type;
private Type type;
private Serializable value;
public Preference(String key, Preferences.PreferenceType type, Serializable value) {
public Preference(String key, Type type, Serializable value) {
this.key = key;
this.type = type;
this.value = value;
......@@ -31,7 +31,7 @@ public class PreferenceAnswer extends DataExchanger.DataExchangerAnswer<Preferen
return key;
}
public Preferences.PreferenceType getType() {
public Type getType() {
return type;
}
......
......@@ -49,7 +49,7 @@ public class ContactList implements Serializable{
private static Contact tryReadContact(@NonNull ObjectInputStream in){
try{
return (Contact)in.readObject();
}catch(Exception e){
}catch(Exception ignored){
}
return null;
}
......
......@@ -5,19 +5,19 @@ import android.database.Cursor;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Build;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.frostnerd.utils.database.orm.Entity;
import com.frostnerd.utils.database.orm.MultitonEntity;
import com.frostnerd.utils.database.orm.parser.columns.Column;
import com.frostnerd.utils.database.orm.parser.columns.Column;
import com.frostnerd.utils.database.orm.parser.ParsedEntity;
import com.frostnerd.utils.database.orm.exceptions.AbstractEntityException;
import com.frostnerd.utils.database.orm.exceptions.DuplicateTableException;
import com.frostnerd.utils.database.orm.exceptions.EmptyEntityException;
import com.frostnerd.utils.database.orm.exceptions.NoEntityException;
import com.frostnerd.utils.database.orm.parser.ParsedEntity;
import com.frostnerd.utils.database.orm.parser.columns.Column;
import com.frostnerd.utils.database.orm.parser.columns.SimpleColumn;
import com.frostnerd.utils.database.orm.statementoptions.queryoptions.LimitOption;
import com.frostnerd.utils.database.orm.statementoptions.queryoptions.QueryOption;
......@@ -27,7 +27,6 @@ import com.frostnerd.utils.general.ArrayUtil;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
......@@ -46,10 +45,19 @@ public abstract class DatabaseHelper extends SQLiteOpenHelper {
private SQLiteDatabase currentDB;
@NonNull
private TreeMap<Class<? extends Entity>, ParsedEntity> entities = new TreeMap<>(Entity.entityReferenceComparator);
private boolean foreignKeyActive = false;
protected final static MockedContext mock(Context context){
if(context instanceof MockedContext) return (MockedContext) context;
else if(context.getApplicationContext() != null) {
return new MockedContext(context.getApplicationContext());
}
return new MockedContext(context);
}
public DatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,
int version, DatabaseErrorHandler errorHandler, @Nullable Set<Class<? extends Entity>> entities) {
super(context, name, factory, version, errorHandler);
super(mock(context), name, factory, version, errorHandler);
String entityName;
Set<String> tableNames = new HashSet<>();
if (entities != null) for (Class<? extends Entity> clazz : entities) {
......@@ -76,10 +84,18 @@ public abstract class DatabaseHelper extends SQLiteOpenHelper {
this(context, name, null, version, entities);
}
@Override
public synchronized void close() {
currentDB = null;
this.entities.clear();
super.close();
}
@Override
@CallSuper
public final void onCreate(SQLiteDatabase db) {
boolean reset = currentDB == null;
activateForeignKeys(db);
currentDB = db;
onBeforeCreate(db);
db.beginTransaction();
......@@ -96,8 +112,8 @@ public abstract class DatabaseHelper extends SQLiteOpenHelper {
private void properlyPrintCollection(Collection<?> set){
StringBuilder text = new StringBuilder();
text.append("{{");
for(Iterator iterator = set.iterator(); iterator.hasNext();){
text.append("\t").append(iterator.next().toString());
for (Object aSet : set) {
text.append("\t").append(aSet.toString());
text.append("\n");
}
text.append("}}");
......@@ -121,7 +137,16 @@ public abstract class DatabaseHelper extends SQLiteOpenHelper {
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
db.execSQL("PRAGMA foreign_keys=ON;");
activateForeignKeys(db);
}
private void activateForeignKeys(SQLiteDatabase db){
if(!foreignKeyActive && db != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
db.setForeignKeyConstraintsEnabled(true);
}else db.execSQL("PRAGMA foreign_keys=ON;");
foreignKeyActive = true;
}
}
public abstract void onBeforeUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
......@@ -168,12 +193,14 @@ public abstract class DatabaseHelper extends SQLiteOpenHelper {
parsed.insert(this, entity);
}
public <T extends Entity> void insert(@NonNull T entity, T... additionalEntities){
@SafeVarargs
public final <T extends Entity> void insert(@NonNull T entity, T... additionalEntities){
ParsedEntity<T> parsed = ParsedEntity.wrapEntity(entity);
parsed.insert(this, entity, additionalEntities);
}
public <T extends Entity> void insert(@NonNull SQLiteDatabase database, @NonNull T entity, T... additionalEntities){
@SafeVarargs
public final <T extends Entity> void insert(@NonNull SQLiteDatabase database, @NonNull T entity, T... additionalEntities){
ParsedEntity<T> parsed = ParsedEntity.wrapEntity(entity);
parsed.insert(database, this, entity, additionalEntities);
}
......
package com.frostnerd.utils.database;
import android.content.Context;
import android.content.ContextWrapper;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import java.io.File;
/**
* A mocked context which is used inside DatabaseHelper to not store an actual context
*
* The problem here is that a DatabaseHelper might be used across different activities/services and needs to stay open all the time
* (imagine a service running and accessing the database with both read and write whilst an activity needs to do the same
* - they have to use the same DatabaseHelper which would leak either context if the activity/service is terminated
*
* As SQLiteOpenHelper and its subclasses only access getDatabasePath of the context there is no need to override anything else.
*
*/
public final class MockedContext extends ContextWrapper {
private final DatabaseLazyLoader database;
private File path;
private Context context;
/**
* Creates a Mocked context for usage in a SQLiteOpenHelper
* @param base The Context to be used
*/
MockedContext(Context base) {
super(null);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
database = new DatabaseLazyLoader(base);
context = base;
}else {
context = base;
database = null;
}
}
public void destroy(boolean closeDatabase){
if(database != null)database.destroy(closeDatabase);
}
@Override
public File getDatabasePath(String name) {
if(context != null){
path = context.getDatabasePath(name);
context = null;
}
return path;
}
@Override
public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) {
if (database == null)
throw new IllegalStateException("Database is not present");
path = context != null ? context.getDatabasePath(name) : path;
context = null;
return database.getDatabase(name, mode, factory, null);
}
@Override
public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
if (database == null)
throw new IllegalStateException();
path = context != null ? context.getDatabasePath(name) : path;
context = null;
return database.getDatabase(name, mode, factory, errorHandler);
}
/*
Normally you wouldn't rely on finalize - in this case it's only used because as an extra safety measure
*/
@Override
protected void finalize() throws Throwable {
destroy(false);
super.finalize();
}
/**
* Stores the given context on API < O_MR1
*
* It then creates/opens the database once and then discards the context and serves the cached SQLiteDatabase
*/
private final class DatabaseLazyLoader {
private Context context;
private SQLiteDatabase database = null;
private DatabaseLazyLoader(Context context){
this.context = context;
}
/**
* Opens the database once and then discards the context and everything else
* @return The opened database
*/
private SQLiteDatabase getDatabase(String dbName, int mode, SQLiteDatabase.CursorFactory cursorFactory, DatabaseErrorHandler errorHandler){
if(database != null){
if(!database.isOpen())database = null;
return database;
}
database = context.openOrCreateDatabase(dbName, mode, cursorFactory, errorHandler);
context = null;
return database;
}
private void destroy(boolean closeDatabase){
if(closeDatabase && database.isOpen())
database.close();
context = null;
database = null;
}
}
}
\ No newline at end of file
......@@ -62,7 +62,7 @@ public abstract class Entity implements Cloneable{
return name;
}
public static boolean entityHasReferenceTo(Class<? extends Entity> referencer, Class<? extends Entity> referenced, boolean flat){
private static boolean entityHasReferenceTo(Class<? extends Entity> referencer, Class<? extends Entity> referenced, boolean flat){
ArrayList<Class<? extends Entity>> inspection = new ArrayList<>();
inspection.add(referencer);
for(ListIterator<Class<? extends Entity>> iterator = inspection.listIterator(); iterator.hasNext() || iterator.hasPrevious();){
......
......@@ -10,5 +10,5 @@ package com.frostnerd.utils.database.orm;
* development@frostnerd.com
*/
public interface Hashable {
public long createHash();
long createHash();
}
......@@ -23,7 +23,7 @@ public @interface Default {
Direction direction() default Direction.TO_DATABASE;
public enum Direction{
enum Direction{
FROM_DATABASE,TO_DATABASE,BOTH
}
}
......@@ -27,7 +27,7 @@ public @interface ForeignKey{
StrategyOnMultiple strategyOnMultiple() default StrategyOnMultiple.FIRST;
public enum StrategyOnMultiple{
enum StrategyOnMultiple{
FIRST, RANDOM, LAST, EXCEPTION
}
}
......@@ -22,7 +22,7 @@ public @interface Serialized {
@NonNull Class<? extends com.frostnerd.utils.database.orm.Serializer<?>> using();
Scope scope() default Scope.OUTER;
public enum Scope{
enum Scope{
INNER, OUTER
}
}
......@@ -19,6 +19,6 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.FIELD})
public @interface Unique {
public static final String defaultName = "uq_cnstrnt";
String defaultName = "uq_cnstrnt";
@NonNull String name() default defaultName;
}
package com.frostnerd.utils.database.orm.exceptions;
import android.os.Build;
import android.support.annotation.RequiresApi;
/**
* Copyright Daniel Wolf 2017
* All rights reserved.
......@@ -23,6 +26,7 @@ public class ConstraintViolationException extends RuntimeException {
super(cause);
}
@RequiresApi(api = Build.VERSION_CODES.N)
public ConstraintViolationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
......