From 7b928d9a6cb6d2f3d2e51c11712fdcfaad18bfc1 Mon Sep 17 00:00:00 2001 From: yoav codish Date: Thu, 30 Jul 2015 11:11:46 +0300 Subject: [PATCH] Added Custom ID annotation, option to define one of your model's columns as secondary id (Thus adding the option to use the server's id type such as String) for updating existing objects, with the same server id, that were retrieved from the server (objects with a different server id will be inserted normally) --- src/com/activeandroid/Model.java | 157 +++++++++++--------- src/com/activeandroid/TableInfo.java | 48 ++++-- src/com/activeandroid/annotation/Table.java | 3 + src/com/activeandroid/util/Log.java | 2 +- src/com/activeandroid/util/SQLiteUtils.java | 2 +- 5 files changed, 129 insertions(+), 83 deletions(-) diff --git a/src/com/activeandroid/Model.java b/src/com/activeandroid/Model.java index 421426ea3..482fa71dc 100644 --- a/src/com/activeandroid/Model.java +++ b/src/com/activeandroid/Model.java @@ -20,6 +20,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import com.activeandroid.annotation.Table; import com.activeandroid.content.ContentProvider; import com.activeandroid.query.Delete; import com.activeandroid.query.Select; @@ -81,85 +82,99 @@ public final Long save() { field.setAccessible(true); - try { - Object value = field.get(this); - - if (value != null) { - final TypeSerializer typeSerializer = Cache.getParserForType(fieldType); - if (typeSerializer != null) { - // serialize data - value = typeSerializer.serialize(value); - // set new object type - if (value != null) { - fieldType = value.getClass(); - // check that the serializer returned what it promised - if (!fieldType.equals(typeSerializer.getSerializedType())) { - Log.w(String.format("TypeSerializer returned wrong type: expected a %s but got a %s", - typeSerializer.getSerializedType(), fieldType)); - } - } - } - } - - // TODO: Find a smarter way to do this? This if block is necessary because we - // can't know the type until runtime. - if (value == null) { - values.putNull(fieldName); - } - else if (fieldType.equals(Byte.class) || fieldType.equals(byte.class)) { - values.put(fieldName, (Byte) value); - } - else if (fieldType.equals(Short.class) || fieldType.equals(short.class)) { - values.put(fieldName, (Short) value); - } - else if (fieldType.equals(Integer.class) || fieldType.equals(int.class)) { - values.put(fieldName, (Integer) value); - } - else if (fieldType.equals(Long.class) || fieldType.equals(long.class)) { - values.put(fieldName, (Long) value); - } - else if (fieldType.equals(Float.class) || fieldType.equals(float.class)) { - values.put(fieldName, (Float) value); - } - else if (fieldType.equals(Double.class) || fieldType.equals(double.class)) { - values.put(fieldName, (Double) value); - } - else if (fieldType.equals(Boolean.class) || fieldType.equals(boolean.class)) { - values.put(fieldName, (Boolean) value); - } - else if (fieldType.equals(Character.class) || fieldType.equals(char.class)) { - values.put(fieldName, value.toString()); - } - else if (fieldType.equals(String.class)) { - values.put(fieldName, value.toString()); - } - else if (fieldType.equals(Byte[].class) || fieldType.equals(byte[].class)) { - values.put(fieldName, (byte[]) value); - } - else if (ReflectionUtils.isModel(fieldType)) { - values.put(fieldName, ((Model) value).getId()); - } - else if (ReflectionUtils.isSubclassOf(fieldType, Enum.class)) { - values.put(fieldName, ((Enum) value).name()); - } - } - catch (IllegalArgumentException e) { - Log.e(e.getClass().getName(), e); - } - catch (IllegalAccessException e) { - Log.e(e.getClass().getName(), e); - } + // Skip the ID column + if (!fieldName.equals(idName)) { + try { + Object value = field.get(this); + + if (value != null) { + final TypeSerializer typeSerializer = Cache.getParserForType(fieldType); + if (typeSerializer != null) { + // serialize data + value = typeSerializer.serialize(value); + // set new object type + if (value != null) { + fieldType = value.getClass(); + // check that the serializer returned what it promised + if (!fieldType.equals(typeSerializer.getSerializedType())) { + Log.w(String.format("TypeSerializer returned wrong type: expected a %s but got a %s", + typeSerializer.getSerializedType(), fieldType)); + } + } + } + } + + // TODO: Find a smarter way to do this? This if block is necessary because we + // can't know the type until runtime. + if (value == null) { + values.putNull(fieldName); + } else if (fieldType.equals(Byte.class) || fieldType.equals(byte.class)) { + values.put(fieldName, (Byte) value); + } else if (fieldType.equals(Short.class) || fieldType.equals(short.class)) { + values.put(fieldName, (Short) value); + } else if (fieldType.equals(Integer.class) || fieldType.equals(int.class)) { + values.put(fieldName, (Integer) value); + } else if (fieldType.equals(Long.class) || fieldType.equals(long.class)) { + values.put(fieldName, (Long) value); + } else if (fieldType.equals(Float.class) || fieldType.equals(float.class)) { + values.put(fieldName, (Float) value); + } else if (fieldType.equals(Double.class) || fieldType.equals(double.class)) { + values.put(fieldName, (Double) value); + } else if (fieldType.equals(Boolean.class) || fieldType.equals(boolean.class)) { + values.put(fieldName, (Boolean) value); + } else if (fieldType.equals(Character.class) || fieldType.equals(char.class)) { + values.put(fieldName, value.toString()); + } else if (fieldType.equals(String.class)) { + values.put(fieldName, value.toString()); + } else if (fieldType.equals(Byte[].class) || fieldType.equals(byte[].class)) { + values.put(fieldName, (byte[]) value); + } else if (ReflectionUtils.isModel(fieldType)) { + values.put(fieldName, ((Model) value).getId()); + } else if (ReflectionUtils.isSubclassOf(fieldType, Enum.class)) { + values.put(fieldName, ((Enum) value).name()); + } + } catch (IllegalArgumentException e) { + Log.e(e.getClass().getName(), e); + } catch (IllegalAccessException e) { + Log.e(e.getClass().getName(), e); + } + } } if (mId == null) { - mId = db.insert(mTableInfo.getTableName(), null, values); + // Check if the custom id name is defined + if (!mTableInfo.getCustomIdName().equals(Table.DEFAULT_CUSTOM_ID_NAME)) { + + String customIdValue = values.get(mTableInfo.getCustomIdName()).toString(); + + Cursor cursor = db.query(mTableInfo.getTableName(), null, mTableInfo.getCustomIdName() + " = ?", + new String[]{customIdValue}, null, null, null, "1"); + + // Check if a row already exists + if (cursor != null && cursor.getCount() > 0) { + + cursor.moveToFirst(); + mId = cursor.getLong(cursor.getColumnIndex(idName)); + + db.update(mTableInfo.getTableName(), values, mTableInfo.getCustomIdName() + " = ?", + new String[]{customIdValue}); + } + else { + mId = db.insert(mTableInfo.getTableName(), null, values); + } + + cursor.close(); + } + else { + mId = db.insert(mTableInfo.getTableName(), null, values); + } } else { db.update(mTableInfo.getTableName(), values, idName+"=" + mId, null); } Cache.getContext().getContentResolver() - .notifyChange(ContentProvider.createUri(mTableInfo.getType(), mId), null); + .notifyChange(ContentProvider.createUri(mTableInfo.getType(), mId), null); return mId; } @@ -303,7 +318,7 @@ public boolean equals(Object obj) { if (obj instanceof Model && this.mId != null) { final Model other = (Model) obj; - return this.mId.equals(other.mId) + return this.mId.equals(other.mId) && (this.mTableInfo.getTableName().equals(other.mTableInfo.getTableName())); } else { return this == obj; diff --git a/src/com/activeandroid/TableInfo.java b/src/com/activeandroid/TableInfo.java index 32d1ecb3f..33df0a733 100644 --- a/src/com/activeandroid/TableInfo.java +++ b/src/com/activeandroid/TableInfo.java @@ -16,6 +16,14 @@ * limitations under the License. */ +import android.text.TextUtils; +import android.util.Log; + +import com.activeandroid.annotation.Column; +import com.activeandroid.annotation.Table; +import com.activeandroid.util.ReflectionUtils; +import com.activeandroid.util.SQLiteUtils; + import java.lang.reflect.Field; import java.util.Collection; import java.util.Collections; @@ -24,13 +32,6 @@ import java.util.List; import java.util.Map; -import android.text.TextUtils; -import android.util.Log; - -import com.activeandroid.annotation.Column; -import com.activeandroid.annotation.Table; -import com.activeandroid.util.ReflectionUtils; - public final class TableInfo { ////////////////////////////////////////////////////////////////////////////////////// // PRIVATE MEMBERS @@ -39,8 +40,10 @@ public final class TableInfo { private Class mType; private String mTableName; private String mIdName = Table.DEFAULT_ID_NAME; + private String mCustomIdName = Table.DEFAULT_CUSTOM_ID_NAME; + - private Map mColumnNames = new LinkedHashMap(); + private Map mColumnNames = new LinkedHashMap(); ////////////////////////////////////////////////////////////////////////////////////// // CONSTRUCTORS @@ -54,6 +57,7 @@ public TableInfo(Class type) { if (tableAnnotation != null) { mTableName = tableAnnotation.name(); mIdName = tableAnnotation.id(); + mCustomIdName = tableAnnotation.customIdName(); } else { mTableName = type.getSimpleName(); @@ -66,6 +70,9 @@ public TableInfo(Class type) { List fields = new LinkedList(ReflectionUtils.getDeclaredColumnFields(type)); Collections.reverse(fields); + boolean isCustomIdSupplied = !mCustomIdName.equals(Table.DEFAULT_CUSTOM_ID_NAME); + Field customIdField = null; + for (Field field : fields) { if (field.isAnnotationPresent(Column.class)) { final Column columnAnnotation = field.getAnnotation(Column.class); @@ -74,12 +81,30 @@ public TableInfo(Class type) { columnName = field.getName(); } + if (isCustomIdSupplied && columnName.equals(mCustomIdName)) { + customIdField = field; + } + mColumnNames.put(field, columnName); } } + if (isCustomIdSupplied) { + if (customIdField == null) { + Log.e(com.activeandroid.util.Log.sTag, + "Given custom Id doesn't exists in table columns", + new Throwable("Custom Id defined as " + mCustomIdName + ", but doesn't exists in table columns.")); + } + else if (!SQLiteUtils.TYPE_MAP.containsKey(customIdField.getType())) { + Log.e(com.activeandroid.util.Log.sTag, + "Given custom Id is of an illegal type", + new Throwable("Custom Id type " + customIdField.getType() + " isn't a legal id type")); + } + } } + + ////////////////////////////////////////////////////////////////////////////////////// // PUBLIC METHODS ////////////////////////////////////////////////////////////////////////////////////// @@ -96,7 +121,11 @@ public String getIdName() { return mIdName; } - public Collection getFields() { + public String getCustomIdName() { + return mCustomIdName; + } + + public Collection getFields() { return mColumnNames.keySet(); } @@ -104,7 +133,6 @@ public String getColumnName(Field field) { return mColumnNames.get(field); } - private Field getIdField(Class type) { if (type.equals(Model.class)) { try { diff --git a/src/com/activeandroid/annotation/Table.java b/src/com/activeandroid/annotation/Table.java index 541dfbe92..993e7bffc 100644 --- a/src/com/activeandroid/annotation/Table.java +++ b/src/com/activeandroid/annotation/Table.java @@ -28,4 +28,7 @@ public static final String DEFAULT_ID_NAME = "Id"; public String name(); public String id() default DEFAULT_ID_NAME; + + public static final String DEFAULT_CUSTOM_ID_NAME = ""; + public String customIdName() default DEFAULT_CUSTOM_ID_NAME; } diff --git a/src/com/activeandroid/util/Log.java b/src/com/activeandroid/util/Log.java index 3c40a23f5..38aa52c08 100644 --- a/src/com/activeandroid/util/Log.java +++ b/src/com/activeandroid/util/Log.java @@ -21,7 +21,7 @@ public final class Log { // PUBLIC MEMBERS ////////////////////////////////////////////////////////////////////////////////////// - private static String sTag = "ActiveAndroid"; + public static final String sTag = "ActiveAndroid"; private static boolean sEnabled = false; ////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/com/activeandroid/util/SQLiteUtils.java b/src/com/activeandroid/util/SQLiteUtils.java index cbf41eaee..51550a3bf 100644 --- a/src/com/activeandroid/util/SQLiteUtils.java +++ b/src/com/activeandroid/util/SQLiteUtils.java @@ -58,7 +58,7 @@ public enum SQLiteType { ////////////////////////////////////////////////////////////////////////////////////// @SuppressWarnings("serial") - private static final HashMap, SQLiteType> TYPE_MAP = new HashMap, SQLiteType>() { + public static final HashMap, SQLiteType> TYPE_MAP = new HashMap, SQLiteType>() { { put(byte.class, SQLiteType.INTEGER); put(short.class, SQLiteType.INTEGER);