Skip to content

Instantly share code, notes, and snippets.

@iamkingalvarado
Created June 8, 2017 22:03
Show Gist options
  • Save iamkingalvarado/e2cb47757815779216197e8541b3af90 to your computer and use it in GitHub Desktop.
Save iamkingalvarado/e2cb47757815779216197e8541b3af90 to your computer and use it in GitHub Desktop.
RealmKeys
package io.dflabs.realm.realmkeys;
import android.content.Context;
import android.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import dalvik.system.DexFile;
import dalvik.system.PathClassLoader;
import io.realm.RealmModel;
/**
* Created by dflabs on 6/8/17.
* RealmCascadeDelete
*/
abstract class ClassScanner {
private static final String TAG = "ClassScanner";
private Context mContext;
ClassScanner(Context context) {
mContext = context;
}
Context getContext() {
return mContext;
}
@SuppressWarnings("unchecked")
void scan() throws IOException, NoSuchMethodException {
long timeBegin = System.currentTimeMillis();
PathClassLoader classLoader = (PathClassLoader) getContext().getClassLoader();
//PathClassLoader classLoader = (PathClassLoader) Thread.currentThread().getContextClassLoader();//This also works good
DexFile dexFile = new DexFile(getContext().getPackageCodePath());
Enumeration<String> classNames = dexFile.entries();
ArrayList<Class<? extends RealmModel>> classes = new ArrayList<>();
while (classNames.hasMoreElements()) {
String className = classNames.nextElement();
if (isTargetClassName(className)) {
Class<? extends RealmModel> aClass;
try {
aClass = (Class<? extends RealmModel>) classLoader.loadClass(className);
if (isTargetClass(aClass)) {
classes.add(aClass);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
onScanResult(classes);
long timeEnd = System.currentTimeMillis();
long timeElapsed = timeEnd - timeBegin;
Log.d(TAG, "scan() cost " + timeElapsed + "ms");
}
protected abstract boolean isTargetClassName(String className);
protected abstract boolean isTargetClass(Class clazz);
abstract void onScanResult(ArrayList<Class<? extends RealmModel>> classes);
}
package io.dflabs.realm.realmkeys;
import android.content.Context;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import io.realm.Realm;
import io.realm.RealmModel;
import io.realm.RealmObject;
import io.realm.RealmQuery;
import io.realm.RealmResults;
import io.realm.internal.RealmObjectProxy;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Created by dflabs on 6/7/17.
* RealmCascadeDelete
*/
public class RealmKeys {
private static HashMap<String, ArrayList<Field>> annotatedFields = new HashMap<>();
private static ArrayList<Class<? extends RealmModel>> classes = new ArrayList<>();
private static HashMap<String, ArrayList<WhereIAmForeignKey>> whereAmIForeignKey = new HashMap<>();
private static HashMap<String, String> primaryKeys = new HashMap<>();
@SuppressWarnings("unchecked")
public static void deleteCascade(Context context, Realm realm, RealmModel realmModel) throws IllegalAccessException {
if (!validObject(realmModel))
throw new RuntimeException("Model must be managed before deletion");
if (classes.isEmpty()) {
try {
new ClassScanner(context) {
@Override
protected boolean isTargetClassName(String className) {
return className.startsWith(getContext().getPackageName())//I want classes under my package
&& !className.contains("$");
}
@Override
protected boolean isTargetClass(Class clazz) {
return RealmModel.class.isAssignableFrom(clazz);
}
@Override
void onScanResult(ArrayList<Class<? extends RealmModel>> classes) {
RealmKeys.classes.addAll(classes);
}
}.scan();
} catch (IOException | NoSuchMethodException ignored) {
}
}
deleteModel(realm, realmModel);
}
private static void deleteModel(Realm realm, RealmModel realmModel) throws IllegalAccessException {
if (realmModel instanceof RealmObjectProxy) {
realmModel = realm.copyFromRealm(realmModel);
}
String canonical = realmModel.getClass().getCanonicalName();
Object myPrimaryKey = validCachedObject(realmModel);
String primaryKey = primaryKeys.get(canonical);
ArrayList<Field> fields = annotatedFields.get(canonical);
/*
Search and delete related OneToOneObjects
*/
for (Field field : fields) {
Object fieldObject = field.get(realmModel);
if (fieldObject != null) {
if (fieldObject instanceof RealmModel) {
if (field.isAnnotationPresent(RealmForeignKey.class)) {
throw new RuntimeException("RealmForeignKey can only be used with String or Long field");
} else if (field.isAnnotationPresent(RealmOneToOneKey.class)) {
deleteModel(realm, (RealmModel) field.get(realmModel));
/*
Set my OneToOne relation to null
*/
boolean isAccessible = field.isAccessible();
field.setAccessible(true);
field.set(realmModel, null);
field.setAccessible(isAccessible);
realm.copyToRealmOrUpdate(realmModel);
}
} else if (fieldObject instanceof String || fieldObject instanceof Long) {
if (field.isAnnotationPresent(RealmOneToOneKey.class)) {
RealmOneToOneKey oneToOneKey = field.getAnnotation(RealmOneToOneKey.class);
Class<? extends RealmModel> clazz = oneToOneKey.value();
if (!primaryKeys.containsKey(clazz.getCanonicalName())){
for (Field _field : clazz.getDeclaredFields()){
if (_field.isAnnotationPresent(RealmPrimaryKey.class)) {
primaryKeys.put(canonical, _field.getName());
break;
}
}
}
String targetPrimaryKey = primaryKeys.get(clazz.getCanonicalName());
RealmResults<? extends RealmModel> realmResults;
if (fieldObject instanceof String) {
realmResults = realm.where(clazz)
.equalTo(targetPrimaryKey, (String) fieldObject)
.findAll();
} else {
realmResults = realm.where(clazz)
.equalTo(targetPrimaryKey, (Long) fieldObject)
.findAll();
}
/*
Delete ToOne objects recursively too
*/
for (RealmModel realmModel1 : realmResults) {
deleteModel(realm, realmModel1);
}
/*
Set my OneToOne relation to null
*/
boolean isAccessible = field.isAccessible();
field.setAccessible(true);
if (fieldObject instanceof String) field.set(realmModel, null);
else field.set(realmModel, -1L);
field.setAccessible(isAccessible);
realm.copyToRealmOrUpdate(realmModel);
}
} else {
throw new RuntimeException("Only RealmModel object, String or Long fields can be annotated with RealmKeys");
}
}
}
/*
Check if I am OneToOne or ForeignKey for someone and delete that objects too
*/
ArrayList<WhereIAmForeignKey> _whereIAmForeignKey = whereAmIForeignKey.get(canonical);
for (WhereIAmForeignKey iAmForeignKey : _whereIAmForeignKey) {
RealmQuery queryFk = realm.where(iAmForeignKey.clazz);
for (int i = 0; i < iAmForeignKey.fields.size(); i++) {
Field field = iAmForeignKey.fields.get(i);
if (field.isAnnotationPresent(RealmForeignKey.class)) {
if (i > 0 && i - 1 < iAmForeignKey.fields.size()) {
if (myPrimaryKey instanceof String) {
queryFk = queryFk.equalTo(field.getName(), (String) myPrimaryKey).or();
} else if (myPrimaryKey instanceof Long) {
queryFk = queryFk.equalTo(field.getName(), (Long) myPrimaryKey).or();
}
} else {
if (myPrimaryKey instanceof String) {
queryFk = queryFk.equalTo(field.getName(), (String) myPrimaryKey);
} else if (myPrimaryKey instanceof Long) {
queryFk = queryFk.equalTo(field.getName(), (Long) myPrimaryKey);
}
}
} else if (field.isAnnotationPresent(RealmOneToOneKey.class)){
RealmResults<? extends RealmModel> results = null;
if (myPrimaryKey instanceof String) {
results = realm.where(iAmForeignKey.clazz).equalTo(field.getName(), (String) myPrimaryKey).findAll();
} else if (myPrimaryKey instanceof Long){
results = realm.where(iAmForeignKey.clazz).equalTo(field.getName(), (Long)myPrimaryKey).findAll();
}
if (results != null) {
for (RealmModel result: results) {
Object fieldObject = field.get(result);
boolean isAccessible = field.isAccessible();
field.setAccessible(true);
if (fieldObject instanceof String) field.set(result, null);
else field.set(result, -1L);
field.setAccessible(isAccessible);
realm.copyToRealmOrUpdate(result);
}
}
}
}
queryFk.findAll().deleteAllFromRealm();
}
/*
Delete myself
*/
if (RealmObject.isManaged(realmModel)) {
RealmObject.deleteFromRealm(realmModel);
} else {
RealmModel model = null;
if (myPrimaryKey instanceof String) {
model = realm.where(realmModel.getClass()).equalTo(primaryKey, (String) myPrimaryKey).findFirst();
} else if (myPrimaryKey instanceof Long) {
model = realm.where(realmModel.getClass()).equalTo(primaryKey, (Long) myPrimaryKey).findFirst();
}
if (model != null) {
RealmObject.deleteFromRealm(model);
}
}
}
private static Object validCachedObject(RealmModel realmModel) throws IllegalAccessException {
String canonical = realmModel.getClass().getCanonicalName();
Object myPrimaryKey = null;
if (!annotatedFields.containsKey(canonical)) {
ArrayList<Field> fields = new ArrayList<>();
for (Field field : realmModel.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(RealmForeignKey.class) || field.isAnnotationPresent(RealmOneToOneKey.class)) {
fields.add(field);
}
if (field.isAnnotationPresent(RealmPrimaryKey.class)) {
if (!primaryKeys.containsKey(canonical)) {
primaryKeys.put(canonical, field.getName());
}
fields.add(field);
}
}
annotatedFields.put(canonical, fields);
}
ArrayList<Field> fields = annotatedFields.get(canonical);
for (Field field : fields){
if (field.isAnnotationPresent(RealmPrimaryKey.class)){
myPrimaryKey = field.get(realmModel);
break;
}
}
if (myPrimaryKey == null) {
throw new RuntimeException("RealmModel must implement RealmPrimaryKey");
}
if (!whereAmIForeignKey.containsKey(canonical)) {
ArrayList<WhereIAmForeignKey> _classes = new ArrayList<>();
for (Class<? extends RealmModel> _clazz : classes) {
WhereIAmForeignKey _whereIAmForeignKey = new WhereIAmForeignKey(_clazz);
for (Field _field : _clazz.getDeclaredFields()) {
if (_field.isAnnotationPresent(RealmForeignKey.class)) {
RealmForeignKey foreignKey = _field.getAnnotation(RealmForeignKey.class);
if (foreignKey.value() == realmModel.getClass()) {
_whereIAmForeignKey.addField(_field);
}
}
if (_field.isAnnotationPresent(RealmOneToOneKey.class)) {
RealmOneToOneKey foreignKey = _field.getAnnotation(RealmOneToOneKey.class);
if (foreignKey.value() == realmModel.getClass()) {
_whereIAmForeignKey.addField(_field);
}
}
}
if (_whereIAmForeignKey.fields.size() > 0)
_classes.add(_whereIAmForeignKey);
}
whereAmIForeignKey.put(canonical, _classes);
}
return myPrimaryKey;
}
private static boolean validObject(RealmModel realmModel) {
return RealmObject.isValid(realmModel) && RealmObject.isManaged(realmModel);
}
/**
* Created by dflabs on 6/7/17.
*/
@Target(FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RealmForeignKey {
Class<? extends RealmModel> value() default RealmModel.class;
}
/**
* Created by dflabs on 6/7/17.
* RealmCascadeDelete
*/
@Target(FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RealmOneToOneKey {
Class<? extends RealmModel> value() default RealmModel.class;
}
/**
* Created by dflabs on 6/8/17.
* RealmCascadeDelete
*/
@Retention(RUNTIME)
@Target(FIELD)
public @interface RealmPrimaryKey {
}
private static class WhereIAmForeignKey {
private Class<? extends RealmModel> clazz;
private ArrayList<Field> fields;
WhereIAmForeignKey(Class<? extends RealmModel> clazz) {
this.clazz = clazz;
this.fields = new ArrayList<>();
}
void addField(Field field) {
this.fields.add(field);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment