Created
June 8, 2017 22:03
-
-
Save iamkingalvarado/e2cb47757815779216197e8541b3af90 to your computer and use it in GitHub Desktop.
RealmKeys
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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